The PEAK Developers' Center   Diff for "HelloWorld" UserPreferences
 
HelpContents Search Diffs Info Edit Subscribe XML Print View
Ignore changes in the amount of whitespace

Differences between version dated 2003-12-08 03:31:11 and 2003-12-09 22:22:19 (spanning 20 versions)

Deletions are marked like this.
Additions are marked like this.

#DEPRECATED
 
Please use IntroToPeak instead.
 
By R. David Murray (rdmurray at bitdance.com) (w/edits by PJE)
 
'''Preface'''

write up your question on the PEAK mailing list, and we'll try to
get these examples updated with more Windows-specific command lines.)
 
(Note to all readers: the examples in this document have been tested
against PEAK CVS. The examples will not work with 0.5 alpha 2.
There could also be synchronization errors between the examples and
the test code, so if you find something that doesn't work as
advertised when run against current CVS, please let me know.)
 
'''Table of Contents'''
 
[[TableOfContents]]

class HelloWorld(commands.AbstractCommand):
 
    def _run(self):
        print "Hello, world!"
        print >>self.stdout, "Hello, world!"
 
}}} Hmm, what's that output redirection? Doesn't print normally
write to `stdout`?
 
}}} So, again, we can run our program at this point and get the same
Indeed, it does. And `stdout` is normally the shell command's
`stdout`, and certainly is in our little application here. But an
`AbstractCommand` can be run in many environments, not just from a
shell script the way we are doing here. So to future-proof our
code, we use the facilities `AbstractCommand` provides for communicating
with its environment, whatever that may turn out to be.
 
(In addition to `self.stdout`, `AbstractCommand` also provides
`self.stdin`, `self.stderr`, `self.environ`, and `self.argv`.)
 
So, again, we can run our program at this point and get the same
results as before. But we still haven't '''used''' the configuration
information. To do that, we need the `binding` package, to "bind"
an attribute of our application to a configuration variable.

    message = binding.Obtain(PropertyName('helloworld.message'))
 
    def _run(self):
        print self.message
        print >>self.stdout, self.message
 
}}} With this version of `helloworld.py` we are finally using the
configuration information provided in the `helloworld` file. To prove

 
  * The return value of an executed function (or `_run()` method) will become the exit code for the entire program, when it is run by the `peak` script.
 
  * `_run` implementations should use the variables provided in `self` to communicate with their environment.
 
 * Configuration files
 
  * PEAK's configuration framework supports `.ini`-style configuration files, using property names as section headings, and Python expressions for supplied values. (Note: PEAK also supports other configuration formats, but the `.ini` format is most useful for configuration done by developers. Other formats (such as ZConfig) may be more suitable for end-users.)

    message = binding.Obtain(PropertyName('helloworld.message'))
 
    def _run(self):
        print self.message % self.argv[1]
        print >>self.stdout, self.message % self.argv[1]
 
}}} Note the use of `self.argv` to get access to the command
arguments.
 
}}} The corresponding `hello` file would look like this: {{{
The corresponding `hello` file would look like this: {{{
 
#!/usr/bin/env peak runIni
 

Hello, world. How are you today?
% ./hello fred
Hello, fred. How are you today?
% ./hello, Klause
% ./hello Klause
Hello, Klause. How are you today?
 
}}}

 
    def _run(self):
        storage.beginTransaction(self)
        print self.Messages[self.argv[1]].text
        print >>self.stdout, self.Messages[self.argv[1]].text
        storage.commitTransaction(self)
 
}}} As you can see, our main program has stayed fairly simple,

We're stretching pretty hard here to find ways to use "Hello, world!"
to demonstrate PEAK concepts. Just keep supressing your impulse
to laugh at the triviality of the example, and keep focused on the
concepts it is demonstrating.
concepts it's demonstrating.
 
At this point our `hello` program can greet anything whose name
is recorded in the table. Suppose we have a new thing we want

Another abstract class provided by PEAK is `AbstractInterpreter`.
This class represents something PEAK can run that will execute
subcommands based on the first argument word. PEAK also provides
an implementation of this class, `Bootstrap`, that we can reuse
to our advantage. So, our `AbstractCommand` from the previous
examples is now going to become the implementation for one of
our two subcommands. We'll have a new object that we'll be
having peak call as our "main program": {{{
an subclass of `AbstractInterpreter` called `Bootstrap`, that looks
up a URL or command shortcut and runs it.
 
If you think that sounds a lot like what the `peak` script does, you're
quite correct. If you take a look at the actual `peak` script, it looks
something like: {{{
#!/usr/bin/env python2.2
 
from peak.running import commands
commands.runMain( commands.Bootstrap )
}}}
 
Which means that we've actually been using `Bootstrap` all along, to
run our programs. Since we'd like to be able to use commands like
`hello to` and `hello for` in the same way that we can use `peak help`
or `peak runIni`, we'll make our new main program a subclass of
`commands.Bootstrap`: {{{
#!python
from peak.api import *
from hello_model import Message

"""
 
    Messages = bindings.Make(
        'hello_storage:MessageDM',
        offerAs=[storage.DMFor(Message)]
        )
        'hello_storage.MessageDM', offerAs=[storage.DMFor(Message)]
    )
 
 
class toCmd(commands.AbstractCommand):

Displays the greeting for "name".
"""
 
    Messages = binding.Obtain(storage.DMFor(Messages))
    Messages = binding.Obtain(storage.DMFor(Message))
 
    def _run(self):
        storage.beginTransaction(self)
        print self.Messages[self.argv[1]].text
        print >>self.stdout, self.Messages[self.argv[1]].text
        storage.commitTransaction(self)
 
}}} So we've got a new `HelloWorld` main program, this time a
subclass of `commands.Bootstrap`. `HelloWorld` is still the holder
for the `MessageDM` binding. PEAK will make the subcommands children
of our `AbstractInterpreter`, so the subcommands will have this DM
binding in their context.
for the `MessageDM` binding. The `Bootstrap` class will automatically
make the subcommands into children of our `HelloWorld` component, so
the subcommands will be able to `Obtain` the DM from their context.
 
The only other thing it's got is a `usage` class variable.
To see how this is used, try typing `./hello` at your

it `toCmd`. And, since it a different class from `HelloWorld` where
we defined the binding to our `MessageDM`, it needs to `Obtain`
that binding. Remember, it is thereby getting access to the
'''same''' `MessageDM` instance as the one referenced by the
descriptor in the `HelloWorld` class, and as will be referenced by
our other subcommand once we write it.
 
Well, it will once we hook it up so that it can actually be invoked
as a subcommand.
 
To do that, we create a new binding. We could do this in our python
code, but we'll do it in the configuration file to show how
flexible PEAK commands can be (imagine tailoring a version of a
command to have a restricted set of subcommands for use by a
particular user group just by creating a different config file to
call it through): {{{
'''same''' `MessageDM` instance as the one in the associated
`HelloWorld` instance. So, by using `Make(...,offerAs=[something])`
in a parent component, and `Obtain(something)` in child components,
a parent component can share one instance of a service with any
child component that needs it.
 
== peak.running.shortcuts ==
 
Next, we need to hook up the `toCmd` class so it can be invoked as
a subcommand. How can we do that? Remember when we looked at the
help for the `peak` script? The first paragraph said: {{{
 
The 'peak' script bootstraps and runs a specified command object or command
class. The NAME_OR_URL argument may be a shortcut name defined in the
'peak.running.shortcuts' property namespace, or a URL of a type
supported by 'peak.naming'.
 
[peak.running.shortcuts]
}}}
 
to = importString('helloworld:toCmd')
Hm. If we look at the `peak.ini` file, we see a section called
`[peak.running.shortcuts]`, containing a bunch of properties called
`runIni`, `help`, and many other commands.
 
Does this mean that if we add a similar section to our `hello` file,
we can create subcommands of our own? Let's try adding this new
section to `hello`: {{{
 
[peak.running.shortcuts]
to = importString('helloworld.toCmd')
 
}}}
 
With the above added to our `hello` file, we can now
do things like: {{{
and now try it: {{{
 
    % ./hello to Fred
    Greetings, good sir.
 
}}}
 
Here's a slightly subtle point you may not have noticed: when we
turned our command into a subcommand we did ''not'' need to change
our argv index number. The argv array stored in self.argv of the
subcommand has the subcommand name in `argv[0]`, and the rest of
the arguments starting in `argv[1]`.
Excellent! By the way, you may have noticed that when we
turned our command into a subcommand, we did ''not'' need to change
our `argv` index number. The argument array stored in `self.argv`
of the subcommand has the subcommand name in `argv[0]`, and the rest
of the arguments starting in `argv[1]`. That's because
`AbstractInterpreter` classes like `Bootstrap` automatically shift
the arguments over for us when they create the subcommand object.
 
Also by the way, we should mention that it wasn't strictly necessary
to edit the configuration file to do what we just did. We also could
have defined a binding in our `HelloWorld` class to "offer" the right
configuration value, like this: {{{
 
    __toCmd = binding.Obtain(
        'import:helloworld.toCmd',
        offerAs=['peak.running.shortcuts.to']
    )
 
}}}
 
But now that you've seen how, you can also see why we didn't do it.
It's rather ugly to do this sort of configuration in code, compared
to using an `.ini` file. But it's nice to know you can do it if you
need to.
 
Of course, the configuration file is also more flexible: notice, for
example, that we could make multiple configuration files for the same
code, each file specifying a different set of subcommands, perhaps
for different users of the app. You could almost say that PEAK's
motto is, "code reuse, through flexibility".
 
Now try this: {{{
Now for something completely different. Let's try this: {{{
 
    % ./hello to
 

in the code above, right? Well, an `AbstractCommand` doesn't
automacially display a usage when no arguments are supplied because,
after all, no arguments might be required. It '''will''' automatically
display the usage if we raise a `commands.InvocationError`, though:
display the usage if we raise a `commands.InvocationError`, in our
`_run` method, though:
{{{
#!python
def _run(self):
    if len(self.argv)<2: raise commands.InvocationError("Missing name")
    storage.beginTransaction(self)
    print self.Messages[self.argv[1]].text
    storage.commitTransaction(self)
 
    def _run(self):
        if len(self.argv)<2:
            raise commands.InvocationError("Missing name")
        storage.beginTransaction(self)
        print >>self.stdout, self.Messages[self.argv[1]].text
        storage.commitTransaction(self)
}}}
 
Now we'll get: {{{

 
Displays the greeting for "name".
 
 
to: Missing name
 
}}}
 
There's just one problem left with the `hello` command. Try running
`hello runIni`, and see what we get: {{{
 
% ./hello runIni
 
Usage: peak runIni CONFIG_FILE arguments...
 
CONFIG_FILE should be a file in the format used by 'peak.ini'. (Note that
it does not have to be named with an '.ini' extension.) The file should
define a 'running.IExecutable' for the value of its 'peak.running.app'
property. The specified 'IExecutable' will then be run with the remaining
command-line arguments.
 
 
runIni: missing argument(s)
 
}}}
 
Whoops! Just because our configuration file contains its own
`[peak.running.commands]` section, doesn't mean that the settings in
`peak.ini` don't apply. We need to do something about this, so that
`hello` doesn't reuse all the `peak` subcommands.
 
Looking at `peak.ini`, we sometimes see that properties sometimes
end with a `*`. What happens if we define a `*` rule in the shortcuts
section,? {{{
 
[peak.running.shortcuts]
* = commands.NoSuchSubcommand
to = importString('helloworld.toCmd')
 
}}}
 
Let's try it now: {{{
 
% ./hello runIni
 
Usage: hello command arguments
Available commands:
 
    for -- sets a greeting
    to -- displays a greeting
 
 
runIni: No such subcommand 'runIni'
 
}}}
 
Good. To recap, we used `commands.NoSuchSubcommand`, which
raises an `InvocationError` for us, and we used a `*` rule to
define a default value for properties whose names are within a
particular "property namespace". That is, any name we look up
in `peak.running.shortcuts` from our configuration file, that
isn't explicitly defined there or in our app, will return the
`commands.NoSuchSubcommand` class. That's just what we want
for now.
 
Actually... there is still one more problem. `commands.Bootstrap`
also accepts URLs on the command line by default. `commands.Bootstrap`
provides a way to turn that behvior off, though. We just need to
add a flag variable to our `HelloWorld` class: {{{
#!python
class HelloWorld(commands.Bootstrap):
 
    acceptURLs = False
}}}
 
With these changes, our Bootstrap derivative will now do the right
thing. Let's move on to the `for` command now.
 
== Storing a New Message: the Subcommand ==
== Storing a New Message: the "for" Subcommand ==
 
Now, we know we're going to have to rewrite our `hello_storage.py` to
allow us to write to the databse, but let's start this part of the

completely independent of how we go about initiating the save
in the application program.
 
So, we need another `AbstractCommand` subclass, and another
binding in our `hello` config file: {{{
So, we need another
rule in our `hello` configuration file: {{{
 
[peak.running.shortcuts]
* = commands.NoSuchSubcommand
to = importString('helloworld.toCmd')
for = importString('helloworld.forCmd')
 
to = importString('helloworld:toCmd')
for = importString('helloworld:forCmd')
 
}}}
}}} and another `AbstractCommand` subclass in `helloworld.py`
 
{{{
#!python
 
class forCmd(commands.AbstractCommand):
 
    usage = """

    Messages = binding.Obtain(storage.DMFor(Message))
 
    def _run(self):
        if len(self.argv)<2: raise commands.InvocationError("Missing arguments")
        parts = ' '.join(self.argv[1:]).split(':')
        if len(parts)!=2: raise commands.InvocationError("Bad argument format")
        forname = parts[0].strip(); message = parts[1].strip()
        if len(self.argv)<2:
            raise commands.InvocationError("Missing arguments")
        parts = ' '.join(self.argv[1:]).split(':',1)
        if len(parts)!=2:
            raise commands.InvocationError("Bad argument format")
        forname, message = parts
        storage.beginTransaction(self)
        newmsg = self.Messages.newItem()
        newmsg.forname = forname
        newmsg.text = message
        newmsg.forname = forname.strip()
        newmsg.text = message.strip()
        storage.commitTransaction(self)
 
}}} To put a new object in our database, we ask the Data Manager for

 
Stores "greeting" as the greeting message for "name".
 
 
for: Missing arguments
 
% ./hello for foobar

 
Stores "greeting" as the greeting message for "name".
 
 
for: Bad argument format
 
% ./hello for Jeff: Hi, guy!

abort commit l ls mksub py rd rmsub unset
bind dir ll md mv python rm set
cd help ln mkdir pwd quit rmdir show
}}} Ty originally wanted to call it "peek", so `peak n2` is also
}}} Ty originally wanted to call it "into", so `peak n2` is also
a pun on "peek into".
 
Why is `n2` of interest to us now? One of the n2 adapters avaiable

important: the naming system will look up anything it understands,
but something that uses the naming system to look things up may
have additional constraints on what referenced objects will actually
'''work''' when specified in such a URL.
''work'' when specified in such a URL.
 
PEAK of course makes schemes and production rules completely
configurable. The `peak.naming.schemes` section of the

of data they are really expecting us to interface with.
 
Now, this is all well and good, but so far it hasn't gotten us any
nearer making our program get group data from an SQL database.
But of course, PEAK wouldn't have a naming system if it didn't
use it everywhere, so you probably won't find it too surprising
to that the `naming.Obtain` function ''also'' accepts
anything handled by `peak.naming`, as long as it supports
the IComponentKey interface or can be adapted to it.
nearer making our program get group data from an SQL database. But
PEAK wouldn't have a naming system if it didn't use it everywhere,
of course, so you probably won't find it too surprising to that the
`naming.Obtain` function ''also'' accepts anything handled by
`peak.naming`, as long as it supports the `IComponentKey` interface
or can be adapted to it.
 
Hmm. "...or can be adapted to it." Now explaining '''that''' would
Hmm. "...or can be adapted to it." Now explaining ''that'' would
require quite a long digression. A very important digression if
you really want to understand the essence of PEAK, but one that is
far too long to get in to here. You can read
[http://www.python.org/peps/pep-0246.html PEP 246] and/or the
[http://peak.telecommunity.com/PyProtocols.html PyProtocols]
[http://peak.telecommunity.com/protocol_ref/module-protocols.html
documentation] if you want to delve into it now.
[http://peak.telecommunity.com/protocol_ref/module-protocols.html documentation]
if you want to delve into it now.
 
But all you really '''need''' to know for our purposes here is that
But all you really ''need'' to know for our purposes here is that
within the PEAK system we have a way of adding behavior to objects
in a very flexible way. So if we have an object that represents
a connection to a database, and Obtain wants it to support

another chapter.)
 
What this all means right now is that we can put something
in our HelloWorld class like this: {{{
in our `HelloWorld` class like this: {{{
db = Obtain("sqlite:test.db")
}}}
and like magic our program will have a connection to

% ./hello for vip:"I'm so pleased to see you, %s"
% ./hello to Jeff
I'm so pleased to see you, Jeff
>>>
}}}
Another nice feature worth pointing out, even though it isn't useful
in our simple example, is that the SQL database object participates
in the transaction. (In fact, there has to be an active transaction
for the sql database function to work.) So if our subcommands
were in fact updating the SQL database, both the changes to it and
the changes to our EditableFile would be kept consistent across
the changes to our `EditableFile` would be kept consistent across
PEAK transaction boundaries. If our PEAK transaction aborts,
''all'' database changes, to whichever database, are rolled back
(assuming the database supports transaction abort, of course).

If you think about what you just learned about how PEAK can be used
to tie together disparate, possibly legacy databases in the corporate
setting, and maintain enterprise-level control over the integrity
of the data, you can see why PEAK is an "'''Enterprise''' Application
of the data, you can see why PEAK is an "''Enterprise'' Application
Toolkit".
 
 

 
    def __contains__(self, oid):
        #using where would be more efficient here, but I don't
        #no how to test the cursor to see if I got any results
        #know how to test the cursor to see if I got any results
        for row in self.messagedb('select name from messages'):
            if row.name==oid: return 1
        return 0

    def _run(self):
        if len(self.argv)<2: raise commands.InvocationError("Missing name")
        storage.beginTransaction(self)
        print self.Customers[self.argv[1]].group.text
        print >>self.stdout, self.Customers[self.argv[1]].group.text
        storage.commitTransaction(self)
 
 

}}}
 
Now our `print` line in `helloworld.py` becomes:{{{
print self.Customers[self.argv[1]].greeting()
print >>self.stdout, self.Customers[self.argv[1]].greeting()
}}} Which is more Demeter-proof anyway.
 
And now everyone is happy:{{{

Greetings, Your Excelency!
}}}
 
 
= Logging =
 
(This chapter has not be edited by PJE)
 
So, our application is deployed. People are using it. But who?
And how much? And just who was it who changed the greeting
for vvips to "Your most humble servant greets you,
Grand and Mighty Poobah!"? Management would like to know.
 
Well, we can't help them with the last question, because
that's already in the past. But we can start gathering the
data needed to answer these sorts of questions, using
PEAK's logging facilities. So in this chapter, we'll
introduce logging, and show how to incorporate it into
our program.
 
 
== Generating Log Messages ==
 
Keeping with the PEAK philosophy of making things easily
configurable, we're not going to hardcode where our
log messages go. Instead our code will simply ask
the configuration system for a logging object, and
pass that object the log messages.
 
A logger in PEAK is something that provides the `ILogger` interface.
Let's take a look: {{{
% peak help running.ILogger
Help on class ILogger in module peak.running.interfaces:
 
class ILogger(protocols.interfaces.Interface)
 | A PEP 282 "logger" object, minus configuration methods
 |
 | All methods that take 'msg' and positional arguments 'args' will
 | interpolate 'args' into 'msg', so the format is a little like a
 | 'printf' in C. For example, in this code:
 |
 | aLogger.debug("color=%s; number=%d", "blue", 42)
 |
 | the log message will be rendered as '"color=blue; number=42"'. Loggers
 | should not interpolate the message until they have verified that the
 | message will not be trivially suppressed. (For example, if the logger
 | is not accepting messages of the designated priority level.) This avoids
 | needless string processing in code that does a lot of logging calls that
 | are mostly suppressed. (E.g. debug logging.)
 |
 | Methods that take a '**kwargs' keywords argument only accept an 'exc_info'
 | flag as a keyword argument. If 'exc_info' is a true value, exception data
 | from 'sys.exc_info()' is added to the log message.
 |
 | Method resolution order:
 | ILogger
 | protocols.interfaces.Interface
 | __builtin__.object
 |
 | Methods defined here:
 |
 | critical(msg, *args, **kwargs)
 | Log 'msg' w/level CRITICAL
 |
 | debug(msg, *args, **kwargs)
 | Log 'msg' w/level DEBUG
 |
 | error(msg, *args, **kwargs)
 | Log 'msg' w/level ERROR
 |
 | exception(msg, *args)
 | Log 'msg' w/level ERROR, add exception info
 |
 | getEffectiveLevel(lvl)
 | Get minimum priority level required for messages to be accepted
 |
 | info(msg, *args, **kwargs)
 | Log 'msg' w/level INFO
 |
 | isEnabledFor(lvl)
 | Return true if logger will accept messages of level 'lvl'
 |
 | log(lvl, msg, *args, **kwargs)
 | Log 'msg' w/level 'lvl'
 |
 | warning(msg, *args, **kwargs)
 | Log 'msg' w/level WARNING
 |
 | ----------------------------------------------------------------------
 | Data and other attributes inherited from protocols.interfaces.Interface:
 |
 | __dict__ = <dictproxy object>
 | dictionary for instance variables (if defined)
 |
 | __metaclass__ = <class 'protocols.interfaces.InterfaceClass'>
 |
 | __weakref__ = <attribute '__weakref__' of 'Interface' objects>
 | list of weak references to the object (if defined)
 
}}} Hmm. There's a lot of information and capabilities there. We're
interested in the general format of a logging call, and which logging
calls are provided. Looking through the list of methods, things
like `critical`, 'debug`, `warning`, `error`, and `info` all look
handy. The other methods we can ignore for now.
 
So let's modify our `to` command to log who is greeting whom (I'm
going to omit the `usage` text when I show modified versions
of our commands from now on; you've seen that text often
enough already!): {{{
#!python
class toCmd(commands.AbstractCommand):
 
    Customers = binding.Obtain(storage.DMFor(Customer))
    log = binding.Obtain(PropertyName("helloworld.logger"))
 
    def _run(self):
        if len(self.argv)<2: raise commands.InvocationError("Missing name")
        storage.beginTransaction(self)
        print self.Customers[self.argv[1]].greeting()
        self.log.info("%s issued greeting to %s", self.environ['USER'],
            self.argv[1])
        storage.commitTransaction(self)
}}}
 
We've added two lines here. The first sets up a class variable `log`
and binds it to the configuration key `helloworld.logger`. We'll
talk about what we set that key to in the next section. The second
line generates the actual log entry. As you can see, we call the
`info` method of our `ILogger` instance, and pass it a message
containing substitution points, and the values to be substituted.
 
Now let's consider our `for` command: {{{
#!python
class forCmd(commands.AbstractCommand):
 
    Groups = binding.Obtain(storage.DMFor(Group))
    log = binding.Obtain(PropertyName("helloworld.logger"))
 
    def _run(self):
        if len(self.argv)<2: raise commands.InvocationError("Missing arguments")
        parts = ' '.join(self.argv[1:]).split(':')
        if len(parts)!=2: raise commands.InvocationError("Bad argument format")
        groupname = parts[0].strip(); message = parts[1].strip()
        storage.beginTransaction(self)
        if groupname in self.Groups:
            group = self.Groups[groupname]
            self.log.warning('Greeting for group %s changed to "%s" by %s',
                groupname, message, self.environ['USER'])
        else:
            group = self.Groups.newItem()
            group.name = groupname
            self.log.warning('Greeting ("%s") for group %s added by %s',
                message, groupname, self.environ['USER'])
        group.text = message
        storage.commitTransaction(self)
}}}
 
Again we set up the logger. Here we have two possible logged messages,
depending on whether or not this is the first time a message has
been set for the group.
 
This time we're logging at `warning` level, figuring that messages
changes should be relatively rare, and if someone is monitoring the
logs that's probably what they'll be interested in seeing. As
implied by the help information up above, PEAK provides ways of
filtering the generated log messages based on `priority`, of which
`info` and `warning` are two examples. This way, if all we need
to know is who is changing the messages, we can set PEAK to ignore
the `info` messages generated by uses of the `to` command.
 
 
== Logging to the Terminal ==
 
So how do we tell PEAK what we want to log, and where? We do that
by specifying those details when we define what goes in to the
`helloworld.logger` configuration variable.
 
To start with, let's configure the logging information to be
dumped to `stderr`, so we can test our logging code and make
sure it is doing what we want. And since we're testing,
we're going to want to see messages of any priority. `debug`
is the lowest priority, so we'll tell the logger to log
everything from `debug` on up. (We aren't using any
debug calls now, but we might want to add some later
next time we have to do some debugging of our application.)
 
{{{
[helloworld]
 
messagedb = naming.LinkRef('sqlite:messages.db')
logger = logs.LogStream(stream=importString('sys.stderr'), level=logs.DEBUG)
}}}
 
Here we are using PEAK's `LogStream` logger. To work, it needs
to know which stream to write to, so we tell it using
`stream=importString('sys.stderr')`. We need to use `importString`
because `sys.stderr` is an import path. We also tell the logger
the minimum level of message to log. (Message levels are named the
same as the corresponding method names, but using all caps).
 
Let's try it out: {{{
% ./hello to Jeff
I am so happy to see you, Jeff
Dec 09 19:34:12 stage PEAK[12865]: rdmurray issued greeting to Jeff
% ./hello for peon: "Hi, %s"
Dec 09 19:36:49 stage PEAK[12870]: Greeting ("Hi, %s") for group peon added by rdmurray
% ./hello for peon: "Hi, scum"
Dec 09 19:37:49 stage PEAK[12871]: Greeting for group peon changed to "Hi, scum" by rdmurray
}}} As you can see, the logging system automatically adds a timestamp, the
system name, an application name, and a processid, very similar to the
way the unix syslog works.
 
== Other Logging Destinations ==
 
OK, so everything is working. We don't want these messages appearing
on the console when we roll out this new version, so we'd better
redirect the logs. We could have them go to a file: {{{
logger = naming.LinkRef("logfile:helloworld.log?level=INFO")
}}} Lots of different users are using this program, though, and
we don't really want log files in whatever directory they happen
to run the command from. We could specify an absolute path, but
it would have to be one that all the potential users could write
to. This is a practical solution: just have the file be writable
by a group all the users belong to.
 
Many systems have a simpler solution, though, and PEAK supports it:
syslog. Here's how we'd direct our logging to syslog: {{{
logger = naming.LinkRef("syslog:local7")
}}} Here we're telling the logger to send the log messages to
syslog under the `local7` facility. We don't specify a log level,
because that can be controlled by the syslog configuration. That
of course means that PEAK must fully generate and send all
messages, which is an overhead worth thinking about if you
select syslog logging. If you don't want that overhead,
you can specify a level just like for the logfile logger,
and PEAK will only syslog messages at that level and higher.
 
== Multiple Log Destinations ==
 
The Boss is really worried about that unauthorized message change.
He wants to be notified immediatly of any new message changes.
Systems has set up a unix named pipe that they've tied to their
paging system, so anything written to that pipe will get sent to
the boss's pager. But we still need to keep logs of all access,
for those usage statistics he also wants. So we can't just redirect
logging, at `warning` level, to the pipe. We need to send the
logging info to two destinations, with two different filtering
levels.
 
And here's how we do it: {{{
logger = logtee:{syslog:local7},{logfile:/tmp/pageboss?level=WARNING}
}}}
 
(NB: Neither syslog: nor logtee: are currently hooked up in
peak.ini, so these examples don't work/aren't tested)
 
= Exploring the rest of PEAK =
 
One important topic we haven't covered here is the "component hierarchy", although we've alluded to it on occasion by talking about "parent" and "child" components. Generally speaking, a component contained within another component is a "child" component of the container, and PEAK provides mechanisms for components to access their containers to "obtain" configuration or other components. It's also possible to treat the component hierarchy like a kind of file system, using paths to navigate from one component to another.

PythonPowered
ShowText of this page
EditText of this page
FindPage by browsing, title search , text search or an index
Or try one of these actions: AttachFile, DeletePage, LikePages, LocalSiteMap, SpellCheck