[Zope-Coders] new zLOG
Chris McDonough
chrism@zope.com
25 Nov 2002 12:35:55 -0500
--=-ORmJU8kipZgJWzW6xMg+
Content-Type: text/plain
Content-Transfer-Encoding: 7bit
Whoops, wrong file. It's Main.py I wanted to send (it replaces z2.py in
the installer branch).
On Mon, 2002-11-25 at 12:31, Chris McDonough wrote:
> On Mon, 2002-11-25 at 12:20, Guido van Rossum wrote:
> > This doesn't sound right -- if someone calls LOG(), they expect the
> > log message to go somewhere, root or not.
>
> I solved this on this installer branch by creating a "startup handler"
> that both buffers log messages and sends them to stdout. When the
> process setuids, zLOG is initialized and the buffer is flushed to the
> resulting handlers.
>
> > Does anybody actually run Zope as root? AFAIK, all customer projects
> > that I know of run it as designated user "zope".
>
> That's a decision that we can make but in that case we should explicitly
> prevent people from starting as root. Currently there is some halfbaked
> effective user support in Zope, which causes much grief for newbies and
> oldbies alike (especially as it relates to logfiles and filestorage
> files being written as root).
>
> > zdaemon.py calls setuid() before forking off a child process.
> >
> > But zdaemon.py has its own log file, and I like to see its message
> > written rather than disappearing. (This is what got me started on
> > this in the first place.)
>
> You may want to try the startup handler. It's in zLOG.LogHandlers. I
> have attached a file named zope.py that comes from the installer branch
> which might help demonstrate its usage.
>
> - C
>
> ----
>
> ##############################################################################
> #
> # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
> #
> # This software is subject to the provisions of the Zope Public License,
> # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
> # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
> # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
> # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
> # FOR A PARTICULAR PURPOSE
> #
> ##############################################################################
> """
> This is a replacement for z2.py which can make use of a config file to obtain
> configuration values (instead of environment variables). It is
> meant to be run from a shell or exec'ed by another program.
>
> Do not change this file unless you know what you're doing. ;-)
> """
>
> import os, sys, getopt, traceback
>
> # assume that this file is sys.argv[0], and that it lives in
> # SOFTWARE_HOME
>
> SOFTWARE_HOME=os.path.split(os.path.abspath(os.path.normpath(sys.argv[0])))[0]
> if SOFTWARE_HOME not in sys.path:
> sys.path.insert(0, SOFTWARE_HOME)
>
> def usage():
> print """
> zope.py [--config-location=filename-or-url] [--sloppy] [--help]
> [--directive=value ...]
>
> zope.py starts a Zope instance.
>
> If the config-location option is not passed, an attempt is made to obtain
> configuration info from a file in the current working directory named
> 'zope.conf;. An alternate absolute or relative filename may be provided, or
> a URL may be provided. The URL should return a file in the same format as the
> filesystem config file.
>
> Directives passed in the options list override those found in the config file.
> An example of a directive is "--client_home=/var/client_home". A
> complete list of directives can be found in any 'zope.conf' config file.
> Directives which compose 'sections' cannot be specified via the
> command-line.
>
> If the 'sloppy' argument is present, duplicate definitions for a non-
> multiple-value-allowed key/value pair in the config file are ignored.
> """
>
> if __name__ == '__main__':
> try:
> from Controller.Directives import DirectiveRegistry, MULTI
> from Controller import Parser
> except ImportError:
> print ("Could not import the Zope 'Controller' package. This "
> "may indicate that the zope.py file has been relocated "
> "from its typical location inside the Zope 'SOFTWARE_HOME'. ")
> usage()
> sys.exit(1)
>
> config_location = 'zope.conf'
> overrides = {}
> sloppy = 0
> # add directives to options list
> directives = DirectiveRegistry.namesByPriority()
> longopts = [ "help", "sloppy", "config="]
> for name in directives:
> longopts.append('%s=' % name)
>
> try:
> opts, args = getopt.getopt(sys.argv[1:], "h", longopts)
> except getopt.GetoptError, v:
> print v
> usage()
> sys.exit(127)
> for k, v in opts:
> if k in ('-h', '--help'):
> usage()
> sys.exit(0)
> elif k == '--config':
> config_location = v
> elif k == '--sloppy':
> sloppy = 1
> else:
> if k.startswith('--'):
> k = k[2:]
> overrides[k] = v
>
> config = Parser.load(config_location, multi=MULTI, sloppy=sloppy)
> config.update(overrides)
> # reconfigure registry with config derived from file and overrides
> DirectiveRegistry.reconfigure(config)
>
> # activate the directives
> DirectiveRegistry.activateDirectives()
>
> # start Zope
> try:
> from Controller import Main
> Main.start_zope()
> except SystemExit:
> raise
> except:
> try:
> import zLOG
> zLOG.LOG("Zope Initialization", zLOG.PANIC, "Startup exception",
> error=sys.exc_info())
> except:
> pass
> traceback.print_exc()
> # tell zdaemon not to restart us by setting 255 exit code
> sys.exit(255)
>
>
>
>
--=-ORmJU8kipZgJWzW6xMg+
Content-Disposition: attachment; filename=Main.py
Content-Transfer-Encoding: quoted-printable
Content-Type: text/x-python; name=Main.py; charset=ISO-8859-15
###########################################################################=
###
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved=
.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
###########################################################################=
###
""" The main Zope startup code (replaces logic in z2.py) """
import os, sys, socket, re
from types import StringType, IntType
from Directives import DirectiveRegistry as directives
from MetaDirective import ConfigurationError
from lock_file import lock_file
_marker =3D []
def start_zope():
check_python_version()
# directives *must* include zope_home, software_home and instance_home
ZOPE_HOME =3D directives['zope_home']
SOFTWARE_HOME =3D directives['software_home']
INSTANCE_HOME =3D directives['instance_home']
# all other directives are optional
NUMBER_OF_THREADS =3D directives['zserver_threads']
IP_ADDRESS =3D directives['ip_address']
DNS_IP =3D directives['dns_ip_address']
UID =3D directives['effective_user']
LOCALE_ID =3D directives['locale']
READ_ONLY =3D directives['zserver_read_only_mode']
DEBUG_MODE =3D directives['debug_mode']
MODULE =3D 'Zope' # removed settability
# can't use a daemon monitor process on windows because windows
# doesn't have os.fork
USE_DAEMON =3D (os.name =3D=3D 'posix' and
directives['use_daemon_process'])
PID_FILENAME =3D directives['pid_filename']
# access the database directive to raise an error if there is a
# semantics problem in the config file. We dont actually use it here,
# it's used in the __init__ of the Zope package.
# this should do better error detection too.
DATABASE_FACTORY =3D directives.get('zodb_databases').get('/')
# we need a separate lock file because on win32, locks are not advisory=
,
# otherwise we would just use the pid file
LOCK_FILENAME =3D (directives['lock_filename'] or
os.path.join(INSTANCE_HOME, 'var', 'Z2.lock'))
if ZOPE_HOME not in sys.path:
sys.path.insert(0, ZOPE_HOME)
if SOFTWARE_HOME not in sys.path:
sys.path.insert(0, SOFTWARE_HOME)
sys.path =3D filter(None, sys.path) # strip empties out of sys.path
# set up our initial logging environment (log everything to stderr
# if we're not in debug mode).
import zLOG
from zLOG import logging
from zLOG.LogHandlers import StartupHandler
# we log events to the root logger, which is backed by a
# "StartupHandler" log handler. The "StartupHandler" outputs to
# stderr but also buffers log messages. When the "real" loggers
# are set up, we flush accumulated messages in StartupHandler's
# buffers to the real logger.
STARTUP_HANDLER =3D StartupHandler(sys.stderr)
if not DEBUG_MODE:
# prevent startup messages from going to stderr if we're not
# in debug mode
if os.path.exists('/dev/null'): # unix
devnull =3D '/dev/null'
else: # win32
devnull =3D 'nul:'
STARTUP_HANDLER =3D StartupHandler(open(devnull, 'w'))
LOGGER_NAMES =3D ('', ) # set up our root logger only (others will prop=
agate)
for name in LOGGER_NAMES:
logger =3D logging.getLogger(name)
logger.addHandler(STARTUP_HANDLER)
do_locale(LOCALE_ID)
# goofy source_port clients business
dav_clients =3D directives['webdav_source_user_agents']
if dav_clients is not None:
sys.WEBDAV_SOURCE_PORT_CLIENTS =3D re.compile(dav_clients).search
# make sure to import zdaemon before zserver or weird things
# begin to happen
import zdaemon
# Import ZServer before we open the database or get at interesting
# application code so that ZServer's asyncore gets to be the
# official one. Also gets SOFTWARE_HOME, INSTANCE_HOME, and CLIENT_HOME
import ZServer
# Increase the number of threads
from ZServer import setNumberOfThreads
setNumberOfThreads(NUMBER_OF_THREADS)
# if we're not using ZDaemon or if we're the child of a Zdaemon process=
,
# start ZServer servers before we setuid so we can bind to low ports
if not USE_DAEMON or (USE_DAEMON and os.environ.get('ZDAEMON_MANAGED'))=
:
# activate our servers
servers =3D directives['servers']
# do stuff that only applies to posix platforms (setuid, daemonizing)
if os.name =3D=3D 'posix':
do_posix_stuff(USE_DAEMON, READ_ONLY, PID_FILENAME, UID)
# Import Zope
if MODULE =3D=3D 'Zope':
import Zope
Zope.startup()
else:
exec "import "+MODULE in {}
# only a small lie, but we want to do this before we configure logging
zLOG.LOG('Zope', zLOG.INFO, 'Ready to handle requests')
if not READ_ONLY:
# lock_file is used for the benefit of zctl, so it can tell whether
# Zope is already running before attempting to fire it off again.
# We aren't concerned about locking the file to protect against
# other Zope instances running from our CLIENT_HOME, we just
# try to lock the file to signal that zctl should not try to
# start Zope if *it* can't lock the file; we don't panic
# if we can't lock it.
try:
if os.path.exists(LOCK_FILENAME):
os.unlink(LOCK_FILENAME)
LOCK_FILE =3D open(LOCK_FILENAME, 'w')
lock_file(LOCK_FILE)
except IOError:
pass
=20
# write pid file if zdaemon didn't do it already
if not USE_DAEMON:
pf =3D open(PID_FILENAME, 'w')
pid=3D'%s\n' % os.getpid()
pf.write(pid)
pf.close()
# now that we've successfully setuid'd, we can log to
# somewhere other than stderr. We rely on directives
# to set up our logging properly.
logging =3D directives['logging']
for logger_name, logger_factory in logging.items():
logger =3D logger_factory() # activate the logger
# flush buffered startup messages to event logger
if logger_name =3D=3D 'event':
STARTUP_HANDLER.flushBufferTo(logger)
# Start Medusa, Ye Hass!
sys.ZServerExitCode=3D0
try:
import asyncore
asyncore.loop()
sys.exit(sys.ZServerExitCode)
finally:
if not READ_ONLY:
try:
os.unlink(PID_FILENAME)
except OSError:
pass
try:
LOCK_FILE.close()
os.unlink(LOCK_FILENAME)
except OSError:
pass
def _warn_nobody():
import zLOG
zLOG.LOG("Zope", zLOG.INFO, ("Running Zope as 'nobody' can compromise "
"your Zope files; consider using a "
"dedicated user account for Zope"))
def check_python_version():
# check for Python version
# too chicken to preclude 2.1 yet
python_version =3D sys.version.split()[0]
optimum_version =3D '2.2.2'
if python_version < '2.1':
raise ConfigurationError, 'Invalid python version %s' % python_vers=
ion
if python_version[:3] =3D=3D '2.1':
err =3D ('You are running Python version %s. Zope may work under t=
his '
'Python version, but it may not. Consider upgrading to '
'Python %s' % (python_version, optimum_version))
print err
if python_version[:3] =3D=3D '2.2':
if python_version[4:5] < '2':
err =3D ('You are running Python version %s. This Python versi=
on '
'has known bugs that may cause Zope to run improperly. '
'Consider upgrading to Python %s' %
(python_version, optimum_version))
print err
def do_posix_stuff(USE_DAEMON, READ_ONLY, PID_FILE, UID):
import zLOG
import zdaemon
import pwd
from Signals import Signals
Signals.registerZopeSignals()
# Warn if we were started as nobody.
if os.getuid():
if pwd.getpwuid(os.getuid())[0] =3D=3D 'nobody':
_warn_nobody()
# Drop root privileges if we have them, and do some sanity checking
# to make sure we're not starting with an obviously insecure setup.
if os.getuid() =3D=3D 0:
if UID =3D=3D None:
msg =3D ('A user was not specified to setuid to; fix this to '
'start as root (change the effective_user directive '
'in zope.conf)')
zLOG.LOG('Zope', zLOG.PANIC, msg)
raise ConfigurationError, msg
# stuff about client home faults removed (real effective user
# support now)
try:
UID =3D int(UID)
except (TypeError, ValueError):
pass
gid =3D None
if isinstance(UID, StringType):
uid =3D pwd.getpwnam(UID)[2]
gid =3D pwd.getpwnam(UID)[3]
elif isinstance(UID, IntType):
uid =3D pwd.getpwuid(UID)[2]
gid =3D pwd.getpwuid(UID)[3]
UID =3D pwd.getpwuid(UID)[0]
else:
zLOG.LOG("Zope", zLOG.ERROR, ("Can't find UID %s" % UID))
raise ConfigurationError, 'Cant find UID %s' % UID
if UID =3D=3D 'nobody':
_warn_nobody()
if gid is not None:
try:
import initgroups
initgroups.initgroups(UID, gid)
os.setgid(gid)
except OSError:
zLOG.LOG("Zope", zLOG.INFO,
'Could not set group id of effective user',
error=3Dsys.exc_info())
os.setuid(uid)
zLOG.LOG("Zope", zLOG.INFO,
'Set effective user to "%s"' % UID)
# umask is silly, blame POSIX. We have to set it to get its value.
current_umask =3D os.umask(0)
os.umask(current_umask)
if current_umask !=3D 077:
current_umask =3D '%03o' % current_umask
zLOG.LOG("Zope", zLOG.INFO, (
'Your umask of %s may be too permissive; for the security of '
'your Zope data, it is recommended you use 077' % current_umask
))
# try to use a management daemon process. We do this after we setuid s=
o
# we don't write our pidfile out as root.
if USE_DAEMON and not READ_ONLY:
import App.FindHomes
sys.ZMANAGED=3D1
# zdaemon.run creates a process which "manages" the actual Zope
# process (restarts it if it dies). The management process passes
# along signals that it receives to its child.
zdaemon.run(sys.argv, pidfile=3DPID_FILE)
def do_locale(LOCALE_ID):
# workaround to allow unicode encoding conversions in DTML
import codecs
dummy =3D codecs.lookup('iso-8859-1')
if LOCALE_ID is not None:
try:
import locale
except:
raise ConfigurationError, (
'The locale module could not be imported.\n'
'To use localization options, you must ensure\n'
'that the locale module is compiled into your\n'
'Python installation.'
)
try:
locale.setlocale(locale.LC_ALL, LOCALE_ID)
except:
raise ConfigurationError, (
'The specified locale "%s" is not supported by your system.=
\n'
'See your operating system documentation for more\n'
'information on locale support.' % LOCALE_ID
)
=20
--=-ORmJU8kipZgJWzW6xMg+--