##// END OF EJS Templates
logtoprocess: clean-up old comment...
Boris Feld -
r35054:52790352 default
parent child Browse files
Show More
@@ -1,133 +1,131
1 1 # logtoprocess.py - send ui.log() data to a subprocess
2 2 #
3 3 # Copyright 2016 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 """send ui.log() data to a subprocess (EXPERIMENTAL)
8 8
9 9 This extension lets you specify a shell command per ui.log() event,
10 10 sending all remaining arguments to as environment variables to that command.
11 11
12 12 Each positional argument to the method results in a `MSG[N]` key in the
13 13 environment, starting at 1 (so `MSG1`, `MSG2`, etc.). Each keyword argument
14 14 is set as a `OPT_UPPERCASE_KEY` variable (so the key is uppercased, and
15 15 prefixed with `OPT_`). The original event name is passed in the `EVENT`
16 16 environment variable, and the process ID of mercurial is given in `HGPID`.
17 17
18 18 So given a call `ui.log('foo', 'bar', 'baz', spam='eggs'), a script configured
19 19 for the `foo` event can expect an environment with `MSG1=bar`, `MSG2=baz`, and
20 20 `OPT_SPAM=eggs`.
21 21
22 22 Scripts are configured in the `[logtoprocess]` section, each key an event name.
23 23 For example::
24 24
25 25 [logtoprocess]
26 26 commandexception = echo "$MSG2$MSG3" > /var/log/mercurial_exceptions.log
27 27
28 28 would log the warning message and traceback of any failed command dispatch.
29 29
30 30 Scripts are run asynchronously as detached daemon processes; mercurial will
31 31 not ensure that they exit cleanly.
32 32
33 33 """
34 34
35 35 from __future__ import absolute_import
36 36
37 37 import itertools
38 38 import os
39 39 import subprocess
40 40 import sys
41 41
42 42 from mercurial import (
43 43 encoding,
44 44 pycompat,
45 45 )
46 46
47 47 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
48 48 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
49 49 # be specifying the version(s) of Mercurial they are tested with, or
50 50 # leave the attribute unspecified.
51 51 testedwith = 'ships-with-hg-core'
52 52
53 53 def uisetup(ui):
54 54 if pycompat.iswindows:
55 55 # no fork on Windows, but we can create a detached process
56 56 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
57 57 # No stdlib constant exists for this value
58 58 DETACHED_PROCESS = 0x00000008
59 59 _creationflags = DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
60 60
61 61 def runshellcommand(script, env):
62 62 # we can't use close_fds *and* redirect stdin. I'm not sure that we
63 63 # need to because the detached process has no console connection.
64 64 subprocess.Popen(
65 65 script, shell=True, env=env, close_fds=True,
66 66 creationflags=_creationflags)
67 67 else:
68 68 def runshellcommand(script, env):
69 69 # double-fork to completely detach from the parent process
70 70 # based on http://code.activestate.com/recipes/278731
71 71 pid = os.fork()
72 72 if pid:
73 73 # parent
74 74 return
75 75 # subprocess.Popen() forks again, all we need to add is
76 76 # flag the new process as a new session.
77 77 if sys.version_info < (3, 2):
78 78 newsession = {'preexec_fn': os.setsid}
79 79 else:
80 80 newsession = {'start_new_session': True}
81 81 try:
82 82 # connect stdin to devnull to make sure the subprocess can't
83 83 # muck up that stream for mercurial.
84 84 subprocess.Popen(
85 85 script, shell=True, stdin=open(os.devnull, 'r'), env=env,
86 86 close_fds=True, **newsession)
87 87 finally:
88 88 # mission accomplished, this child needs to exit and not
89 89 # continue the hg process here.
90 90 os._exit(0)
91 91
92 92 class logtoprocessui(ui.__class__):
93 93 def log(self, event, *msg, **opts):
94 94 """Map log events to external commands
95 95
96 96 Arguments are passed on as environment variables.
97 97
98 98 """
99 99 script = self.config('logtoprocess', event)
100 100 if script:
101 101 if msg:
102 102 # try to format the log message given the remaining
103 103 # arguments
104 104 try:
105 105 # Python string formatting with % either uses a
106 106 # dictionary *or* tuple, but not both. If we have
107 107 # keyword options, assume we need a mapping.
108 108 formatted = msg[0] % (opts or msg[1:])
109 109 except (TypeError, KeyError):
110 110 # Failed to apply the arguments, ignore
111 111 formatted = msg[0]
112 112 messages = (formatted,) + msg[1:]
113 113 else:
114 114 messages = msg
115 115 # positional arguments are listed as MSG[N] keys in the
116 116 # environment
117 117 msgpairs = (
118 118 ('MSG{0:d}'.format(i), str(m))
119 119 for i, m in enumerate(messages, 1))
120 120 # keyword arguments get prefixed with OPT_ and uppercased
121 121 optpairs = (
122 122 ('OPT_{0}'.format(key.upper()), str(value))
123 123 for key, value in opts.iteritems())
124 124 env = dict(itertools.chain(encoding.environ.items(),
125 125 msgpairs, optpairs),
126 126 EVENT=event, HGPID=str(os.getpid()))
127 # Connect stdin to /dev/null to prevent child processes messing
128 # with mercurial's stdin.
129 127 runshellcommand(script, env)
130 128 return super(logtoprocessui, self).log(event, *msg, **opts)
131 129
132 130 # Replace the class for this instance and all clones created from it:
133 131 ui.__class__ = logtoprocessui
General Comments 0
You need to be logged in to leave comments. Login now