##// END OF EJS Templates
logtoprocess: drop support for ui.log() call with invalid msg arguments (BC)...
Yuya Nishihara -
r40656:d2c997b8 default
parent child Browse files
Show More
@@ -1,90 +1,76 b''
1 # logtoprocess.py - send ui.log() data to a subprocess
1 # logtoprocess.py - send ui.log() data to a subprocess
2 #
2 #
3 # Copyright 2016 Facebook, Inc.
3 # Copyright 2016 Facebook, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 """send ui.log() data to a subprocess (EXPERIMENTAL)
7 """send ui.log() data to a subprocess (EXPERIMENTAL)
8
8
9 This extension lets you specify a shell command per ui.log() event,
9 This extension lets you specify a shell command per ui.log() event,
10 sending all remaining arguments to as environment variables to that command.
10 sending all remaining arguments to as environment variables to that command.
11
11
12 Each positional argument to the method results in a `MSG[N]` key in the
12 Positional arguments construct a log message, which is passed in the `MSG1`
13 environment, starting at 1 (so `MSG1`, `MSG2`, etc.). Each keyword argument
13 environment variables. Each keyword argument is set as a `OPT_UPPERCASE_KEY`
14 is set as a `OPT_UPPERCASE_KEY` variable (so the key is uppercased, and
14 variable (so the key is uppercased, and prefixed with `OPT_`). The original
15 prefixed with `OPT_`). The original event name is passed in the `EVENT`
15 event name is passed in the `EVENT` environment variable, and the process ID
16 environment variable, and the process ID of mercurial is given in `HGPID`.
16 of mercurial is given in `HGPID`.
17
17
18 So given a call `ui.log('foo', 'bar', 'baz', spam='eggs'), a script configured
18 So given a call `ui.log('foo', 'bar %s\n', 'baz', spam='eggs'), a script
19 for the `foo` event can expect an environment with `MSG1=bar`, `MSG2=baz`, and
19 configured for the `foo` event can expect an environment with `MSG1=bar baz`,
20 `OPT_SPAM=eggs`.
20 and `OPT_SPAM=eggs`.
21
21
22 Scripts are configured in the `[logtoprocess]` section, each key an event name.
22 Scripts are configured in the `[logtoprocess]` section, each key an event name.
23 For example::
23 For example::
24
24
25 [logtoprocess]
25 [logtoprocess]
26 commandexception = echo "$MSG2$MSG3" > /var/log/mercurial_exceptions.log
26 commandexception = echo "$MSG1" > /var/log/mercurial_exceptions.log
27
27
28 would log the warning message and traceback of any failed command dispatch.
28 would log the warning message and traceback of any failed command dispatch.
29
29
30 Scripts are run asynchronously as detached daemon processes; mercurial will
30 Scripts are run asynchronously as detached daemon processes; mercurial will
31 not ensure that they exit cleanly.
31 not ensure that they exit cleanly.
32
32
33 """
33 """
34
34
35 from __future__ import absolute_import
35 from __future__ import absolute_import
36
36
37 import os
37 import os
38
38
39 from mercurial import (
39 from mercurial import (
40 pycompat,
40 pycompat,
41 )
41 )
42 from mercurial.utils import (
42 from mercurial.utils import (
43 procutil,
43 procutil,
44 )
44 )
45
45
46 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
46 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
47 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
47 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
48 # be specifying the version(s) of Mercurial they are tested with, or
48 # be specifying the version(s) of Mercurial they are tested with, or
49 # leave the attribute unspecified.
49 # leave the attribute unspecified.
50 testedwith = 'ships-with-hg-core'
50 testedwith = 'ships-with-hg-core'
51
51
52 def uisetup(ui):
52 def uisetup(ui):
53
53
54 class logtoprocessui(ui.__class__):
54 class logtoprocessui(ui.__class__):
55 def log(self, event, *msg, **opts):
55 def log(self, event, *msg, **opts):
56 """Map log events to external commands
56 """Map log events to external commands
57
57
58 Arguments are passed on as environment variables.
58 Arguments are passed on as environment variables.
59
59
60 """
60 """
61 script = self.config('logtoprocess', event)
61 script = self.config('logtoprocess', event)
62 if script:
62 if script:
63 if msg:
64 # try to format the log message given the remaining
65 # arguments
66 try:
67 # Format the message as blackbox does
68 formatted = msg[0] % msg[1:]
69 except (TypeError, KeyError):
70 # Failed to apply the arguments, ignore
71 formatted = msg[0]
72 messages = (formatted,) + msg[1:]
73 else:
74 messages = msg
75 env = {
63 env = {
76 b'EVENT': event,
64 b'EVENT': event,
77 b'HGPID': os.getpid(),
65 b'HGPID': os.getpid(),
66 b'MSG1': msg[0] % msg[1:],
78 }
67 }
79 # positional arguments are listed as MSG[N] keys in the
80 # environment
81 env.update((b'MSG%d' % i, m) for i, m in enumerate(messages, 1))
82 # keyword arguments get prefixed with OPT_ and uppercased
68 # keyword arguments get prefixed with OPT_ and uppercased
83 env.update((b'OPT_%s' % key.upper(), value)
69 env.update((b'OPT_%s' % key.upper(), value)
84 for key, value in pycompat.byteskwargs(opts).items())
70 for key, value in pycompat.byteskwargs(opts).items())
85 fullenv = procutil.shellenviron(env)
71 fullenv = procutil.shellenviron(env)
86 procutil.runbgcommand(script, fullenv, shell=True)
72 procutil.runbgcommand(script, fullenv, shell=True)
87 return super(logtoprocessui, self).log(event, *msg, **opts)
73 return super(logtoprocessui, self).log(event, *msg, **opts)
88
74
89 # Replace the class for this instance and all clones created from it:
75 # Replace the class for this instance and all clones created from it:
90 ui.__class__ = logtoprocessui
76 ui.__class__ = logtoprocessui
@@ -1,126 +1,118 b''
1 #require no-windows
1 #require no-windows
2
2
3 ATTENTION: logtoprocess runs commands asynchronously. Be sure to append "| cat"
3 ATTENTION: logtoprocess runs commands asynchronously. Be sure to append "| cat"
4 to hg commands, to wait for the output, if you want to test its output.
4 to hg commands, to wait for the output, if you want to test its output.
5 Otherwise the test will be flaky.
5 Otherwise the test will be flaky.
6
6
7 Test if logtoprocess correctly captures command-related log calls.
7 Test if logtoprocess correctly captures command-related log calls.
8
8
9 $ hg init
9 $ hg init
10 $ cat > $TESTTMP/foocommand.py << EOF
10 $ cat > $TESTTMP/foocommand.py << EOF
11 > from __future__ import absolute_import
11 > from __future__ import absolute_import
12 > from mercurial import registrar
12 > from mercurial import registrar
13 > cmdtable = {}
13 > cmdtable = {}
14 > command = registrar.command(cmdtable)
14 > command = registrar.command(cmdtable)
15 > configtable = {}
15 > configtable = {}
16 > configitem = registrar.configitem(configtable)
16 > configitem = registrar.configitem(configtable)
17 > configitem(b'logtoprocess', b'foo',
17 > configitem(b'logtoprocess', b'foo',
18 > default=None,
18 > default=None,
19 > )
19 > )
20 > @command(b'foobar', [])
20 > @command(b'foobar', [])
21 > def foo(ui, repo):
21 > def foo(ui, repo):
22 > ui.log(b'foo', b'a message: %s\n', b'spam')
22 > ui.log(b'foo', b'a message: %s\n', b'spam')
23 > EOF
23 > EOF
24 $ cp $HGRCPATH $HGRCPATH.bak
24 $ cp $HGRCPATH $HGRCPATH.bak
25 $ cat >> $HGRCPATH << EOF
25 $ cat >> $HGRCPATH << EOF
26 > [extensions]
26 > [extensions]
27 > logtoprocess=
27 > logtoprocess=
28 > foocommand=$TESTTMP/foocommand.py
28 > foocommand=$TESTTMP/foocommand.py
29 > [logtoprocess]
29 > [logtoprocess]
30 > command=(echo 'logtoprocess command output:';
30 > command=(echo 'logtoprocess command output:';
31 > echo "\$EVENT";
31 > echo "\$EVENT";
32 > echo "\$MSG1";
32 > echo "\$MSG1") > $TESTTMP/command.log
33 > echo "\$MSG2") > $TESTTMP/command.log
34 > commandfinish=(echo 'logtoprocess commandfinish output:';
33 > commandfinish=(echo 'logtoprocess commandfinish output:';
35 > echo "\$EVENT";
34 > echo "\$EVENT";
36 > echo "\$MSG1";
35 > echo "\$MSG1";
37 > echo "\$MSG2";
38 > echo "\$MSG3";
39 > echo "canonical: \$OPT_CANONICAL_COMMAND") > $TESTTMP/commandfinish.log
36 > echo "canonical: \$OPT_CANONICAL_COMMAND") > $TESTTMP/commandfinish.log
40 > foo=(echo 'logtoprocess foo output:';
37 > foo=(echo 'logtoprocess foo output:';
41 > echo "\$EVENT";
38 > echo "\$EVENT";
42 > echo "\$MSG1";
39 > echo "\$MSG1") > $TESTTMP/foo.log
43 > echo "\$MSG2") > $TESTTMP/foo.log
44 > EOF
40 > EOF
45
41
46 Running a command triggers both a ui.log('command') and a
42 Running a command triggers both a ui.log('command') and a
47 ui.log('commandfinish') call. The foo command also uses ui.log.
43 ui.log('commandfinish') call. The foo command also uses ui.log.
48
44
49 Use sort to avoid ordering issues between the various processes we spawn:
45 Use sort to avoid ordering issues between the various processes we spawn:
50 $ hg fooba
46 $ hg fooba
51 $ sleep 1
47 $ sleep 1
52 $ cat $TESTTMP/command.log | sort
48 $ cat $TESTTMP/command.log | sort
53
49
54 command
50 command
55 fooba
51 fooba
56 fooba
57 logtoprocess command output:
52 logtoprocess command output:
58
53
59 #if no-chg
54 #if no-chg
60 $ cat $TESTTMP/commandfinish.log | sort
55 $ cat $TESTTMP/commandfinish.log | sort
61
56
62 0
63 canonical: foobar
57 canonical: foobar
64 commandfinish
58 commandfinish
65 fooba
66 fooba exited 0 after * seconds (glob)
59 fooba exited 0 after * seconds (glob)
67 logtoprocess commandfinish output:
60 logtoprocess commandfinish output:
68 $ cat $TESTTMP/foo.log | sort
61 $ cat $TESTTMP/foo.log | sort
69
62
70 a message: spam
63 a message: spam
71 foo
64 foo
72 logtoprocess foo output:
65 logtoprocess foo output:
73 spam
74 #endif
66 #endif
75
67
76 Confirm that logging blocked time catches stdio properly:
68 Confirm that logging blocked time catches stdio properly:
77 $ cp $HGRCPATH.bak $HGRCPATH
69 $ cp $HGRCPATH.bak $HGRCPATH
78 $ cat >> $HGRCPATH << EOF
70 $ cat >> $HGRCPATH << EOF
79 > [extensions]
71 > [extensions]
80 > logtoprocess=
72 > logtoprocess=
81 > pager=
73 > pager=
82 > [logtoprocess]
74 > [logtoprocess]
83 > uiblocked=echo "\$EVENT stdio \$OPT_STDIO_BLOCKED ms command \$OPT_COMMAND_DURATION ms" > $TESTTMP/uiblocked.log
75 > uiblocked=echo "\$EVENT stdio \$OPT_STDIO_BLOCKED ms command \$OPT_COMMAND_DURATION ms" > $TESTTMP/uiblocked.log
84 > [ui]
76 > [ui]
85 > logblockedtimes=True
77 > logblockedtimes=True
86 > EOF
78 > EOF
87
79
88 $ hg log
80 $ hg log
89 $ sleep 1
81 $ sleep 1
90 $ cat $TESTTMP/uiblocked.log
82 $ cat $TESTTMP/uiblocked.log
91 uiblocked stdio [0-9]+.[0-9]* ms command [0-9]+.[0-9]* ms (re)
83 uiblocked stdio [0-9]+.[0-9]* ms command [0-9]+.[0-9]* ms (re)
92
84
93 Try to confirm that pager wait on logtoprocess:
85 Try to confirm that pager wait on logtoprocess:
94
86
95 Add a script that wait on a file to appears for 5 seconds, if it sees it touch
87 Add a script that wait on a file to appears for 5 seconds, if it sees it touch
96 another file or die after 5 seconds. If the scripts is awaited by hg, the
88 another file or die after 5 seconds. If the scripts is awaited by hg, the
97 script will die after the timeout before we could touch the file and the
89 script will die after the timeout before we could touch the file and the
98 resulting file will not exists. If not, we will touch the file and see it.
90 resulting file will not exists. If not, we will touch the file and see it.
99
91
100 $ cat > $TESTTMP/wait-output.sh << EOF
92 $ cat > $TESTTMP/wait-output.sh << EOF
101 > #!/bin/sh
93 > #!/bin/sh
102 > for i in \`$TESTDIR/seq.py 50\`; do
94 > for i in \`$TESTDIR/seq.py 50\`; do
103 > if [ -f "$TESTTMP/wait-for-touched" ];
95 > if [ -f "$TESTTMP/wait-for-touched" ];
104 > then
96 > then
105 > touch "$TESTTMP/touched";
97 > touch "$TESTTMP/touched";
106 > break;
98 > break;
107 > else
99 > else
108 > sleep 0.1;
100 > sleep 0.1;
109 > fi
101 > fi
110 > done
102 > done
111 > EOF
103 > EOF
112 $ chmod +x $TESTTMP/wait-output.sh
104 $ chmod +x $TESTTMP/wait-output.sh
113
105
114 $ cat >> $HGRCPATH << EOF
106 $ cat >> $HGRCPATH << EOF
115 > [extensions]
107 > [extensions]
116 > logtoprocess=
108 > logtoprocess=
117 > pager=
109 > pager=
118 > [logtoprocess]
110 > [logtoprocess]
119 > commandfinish=$TESTTMP/wait-output.sh
111 > commandfinish=$TESTTMP/wait-output.sh
120 > EOF
112 > EOF
121 $ hg version -q --pager=always
113 $ hg version -q --pager=always
122 Mercurial Distributed SCM (version *) (glob)
114 Mercurial Distributed SCM (version *) (glob)
123 $ touch $TESTTMP/wait-for-touched
115 $ touch $TESTTMP/wait-for-touched
124 $ sleep 0.2
116 $ sleep 0.2
125 $ test -f $TESTTMP/touched && echo "SUCCESS Pager is not waiting on ltp" || echo "FAIL Pager is waiting on ltp"
117 $ test -f $TESTTMP/touched && echo "SUCCESS Pager is not waiting on ltp" || echo "FAIL Pager is waiting on ltp"
126 SUCCESS Pager is not waiting on ltp
118 SUCCESS Pager is not waiting on ltp
General Comments 0
You need to be logged in to leave comments. Login now