Hi!

On 02/22/2014 05:26 PM, Pharo4Stef wrote:
Hi goran

There are only two questions:
        'Why when I load code do I have to open the transcript before to see 
the shadow and undeclared.
        and why I cannot click on an object and jump to the broken code?"

Now with SimpleLogger I imagine that I cannot log compiler shadow, don’t you :).

I am not quite sure what you are saying... Ah, you mean logging objects instead of strings? Sure, I didn't say SimpleLog was BETTER, I just said you didn't look at it and thus may be missing out on nice stuff that you can steal :)

- Using syslog levels, it is very standard and logging these days of clouds do NOT stay on the local machine.
- Stealing the syslog emitter, it is just 20 lines of code.
- Stealing log file rotation perhaps? I think its in there.
- Stealing whatever else seems good.

Having log objects in the image is great.

100 timesRepeat: [Goran repeatAfter: me what: ‘String sucks, string are dead 
objects’]

Well, do remember that logs in a "system world" end up getting routed through syslog (and tons of variations on that theme) and eventually end up in event collecting systems etc - just look at fluentd.org to understand what I mean.

So actually, I would make sure that SimpleLogger could also easily log in JSON, let me quote from fluentd:

"Fluentd treats logs as JSON, a popular machine-readable format. It is written primarily in C with a thin-Ruby wrapper that gives users flexibility.

Fluentd’s performance has been proven in the field: its largest user currently collects logs from 5000+ servers, 5 TB of daily data, handling 50,000 msgs/sec at peak time."

I’m coding in Smalltalk and not in perl!

In fact, now that I am looking at SimpleLog I realize we could have had an emitter that collected objects and just let "message" be whatever - and send asString or asJSON to it when it reaches the emitter instead.

Now if you want to have syslog output this is probably one method definition in 
a new formatter in our nice frameworks.
Do you have an output that shows what is needed for sysLog? because I can add a 
package for sysLog outputter.

Its very short code. Just steal it. I attached all the code as an .st fileout so that you can get it trivially. Its the SLSyslogSender class.

The SLLogWatchDog is a trivial "send an email if a certain level is reached" with some bundling etc.

SLLogFile has log file rotation.

SLLogMorph is the simple search and filter UI.

regards, Göran


Object subclass: #SLLog
        instanceVariableNames: 'emitters guard transcriptMaxLevel 
globalMaxLevel notifierMaxLevel'
        classVariableNames: 'Default UseTranscript'
        poolDictionaries: ''
        category: 'SimpleLog'!
!SLLog commentStamp: 'gk 11/21/2007 09:18' prior: 0!
Nice and simple logging. There are eight messages for logging based on 
severity. These correspond to the eight levels defined in syslog:

7 - debug (Debug-level messages)
6 - info (Informational)
5 - notice (Normal but significant Condition)
4 - warning (Warning Condition)
3 - err (Error Condition)
2 - crit (Critical Conditions)
1 - alert (must be handled immediately)
0 - emerg (System is unusable)
        
The absolutely easiest way to log is like this:

        SLLog warn: 'Oopsidaisy'

...this utilizes the class SLLog as a facade onto a singleton that you reach 
with "SLLog instance". You can also register an instance in global #Log like:

        SLLog useGlobalLog: true
        
...and then you can use:

        Log warn: 'Hey, nicer to typ!!'

We only need to include the actual message in the string, timestamp etc is 
added automatically.
You can also explicitly supply a "sender" object which can be anything you 
like, a symbol or a specific domain object even:

        SLLog warn: 'Yowsa' sender: #email

SLLog uses printString on the sender when producing the log entry, so if you 
use domain objects - make sure they have a unique printOn: method defined so 
that you can distinguish them. See class side category "logging" and "logging 
shorthand" for the available log messages.

By default there is a single emitter registered that logs on Transcript, you 
can turn this default initialization off (for new SLLog instances) with:

        SLLog useTranscript: false

You can also use addTranscript/removeTranscript, addMorphic/removeMorphic etc 
to add and remove known emitters:

        SLLog removeTranscript; addMorphic; addSyslog
        
The above removes default transcript emitter and adds an SLLogMorph as an 
emitter (and opens it), and also a SLSyslogSender emitter that will log onto 
localhost using UDP on port 514, see its class comment for details.

You can set a threshold for the syslog levels that should be logged onto 
Transcript (levels <= threshold are logged) using:

        SLLog transcript transcriptMaxLevel: 4

...and you can set a threshold for raising a notifier (debugger) using:

        SLLog instance notifierMaxLevel: 3

...or set a global threshold for all emitters:

        SLLog instance globalMaxLevel: 4

Global flushing of all emitters can be done using "SLLog flush" and should 
ideally be called regularly by some background process.

Finally take a look at SLLogFile for details on how to add it as an emitter to 
get logging onto file instead of Transcript.!


!SLLog methodsFor: 'emitters' stamp: 'gk 11/21/2007 01:02'!
addEmitter: anObject
        "Add an object as a backend emitter to this log instance."

        (emitters includes: anObject) ifFalse: [emitters add: anObject]! !

!SLLog methodsFor: 'emitters' stamp: 'gk 8/3/2006 09:45'!
emitters
        ^emitters! !

!SLLog methodsFor: 'emitters' stamp: 'gk 2/16/2007 11:37'!
removeEmitter: anObject
        (emitters includes: anObject) ifTrue: [
                emitters remove: anObject.
                SLLog debug: 'Emitter removed: ', anObject asString.
        ]! !

!SLLog methodsFor: 'emitters' stamp: 'gk 11/21/2007 08:40'!
removeEmittersOfClass: aClass
        "Remove all registered emitters of a given class."

        (emitters select: [:each | each class = aClass])
                copy do: [:e | self removeEmitter: e]! !


!SLLog methodsFor: 'emitters-morphic' stamp: 'gk 11/21/2007 08:38'!
addMorphic
        "Create and register a UI for watching logging."

        "self addMorphic"

        self addEmitter: SLLogMorph open! !

!SLLog methodsFor: 'emitters-morphic' stamp: 'gk 11/21/2007 08:59'!
morphic
        "Return first found SLLogMorph."

        ^emitters detect: [:each | each class = SLLogMorph] ifNone: [nil] ! !

!SLLog methodsFor: 'emitters-morphic' stamp: 'gk 11/21/2007 08:39'!
removeMorphic
        "Remove all SLLogMorphs."

        self removeEmittersOfClass: SLLogMorph! !


!SLLog methodsFor: 'emitters-syslog' stamp: 'gk 11/21/2007 01:14'!
addSyslog
        "Just add an SLSyslogSender. Default will be to localhost."

        self addEmitter: SLSyslogSender new! !

!SLLog methodsFor: 'emitters-syslog' stamp: 'gk 11/21/2007 08:40'!
removeSyslog
        "Remove all SLSyslogSenders."
        
        self removeEmittersOfClass: SLSyslogSender! !

!SLLog methodsFor: 'emitters-syslog' stamp: 'gk 11/21/2007 08:36'!
syslog
        "Return first found registered SLSyslogSender."
        
        ^emitters detect: [:each | each class = SLSyslogSender] ifNone: [nil]! !


!SLLog methodsFor: 'emitters-transcript' stamp: 'gk 11/21/2007 01:02'!
addTranscript
        "We use ourself as a Transcript emitter."

        self addEmitter: self! !

!SLLog methodsFor: 'emitters-transcript' stamp: 'gk 11/20/2007 22:46'!
level: level name: symbol sender: sender message: msg
        "Default emitter is to just output in Transcript, so this instance 
doubles
        as a Transcript emitter. We respect the transcriptThreshold."
        
        level <= transcriptMaxLevel ifTrue: [
                Transcript show: DateAndTime now printString,' - ',
                        symbol, '(', level asString, '): ', msg, ' (', sender 
class name, ')'; cr]! !

!SLLog methodsFor: 'emitters-transcript' stamp: 'gk 11/21/2007 01:02'!
removeTranscript
        "We use ourself as a Transcript emitter."

        self removeEmitter: self! !

!SLLog methodsFor: 'emitters-transcript' stamp: 'gk 11/21/2007 08:36'!
transcript
        "We use ourself as a Transcript emitter."

        ^self! !


!SLLog methodsFor: 'emitters-watchdog' stamp: 'gk 11/21/2007 08:42'!
addWatchdog
        self addEmitter: SLLogWatchdog! !

!SLLog methodsFor: 'emitters-watchdog' stamp: 'gk 11/21/2007 08:42'!
removeWatchdog
        self removeEmitter: SLLogWatchdog! !

!SLLog methodsFor: 'emitters-watchdog' stamp: 'gk 11/21/2007 08:59'!
watchdog
        ^SLLogWatchdog! !


!SLLog methodsFor: 'logging' stamp: 'gk 8/30/2006 09:04'!
alert: msg

        self emit: msg level: 1 name: #alert sender: nil! !

!SLLog methodsFor: 'logging' stamp: 'gk 8/30/2006 09:04'!
alert: msg sender: sender

        self emit: msg level: 1 name: #alert sender: sender! !

!SLLog methodsFor: 'logging' stamp: 'gk 3/7/2006 08:23'!
critical ! !

!SLLog methodsFor: 'logging' stamp: 'gk 8/30/2006 09:04'!
critical: msg

        self emit: msg level: 2 name: #critical sender: nil! !

!SLLog methodsFor: 'logging' stamp: 'gk 8/30/2006 09:05'!
critical: msg sender: sender

        self emit: msg level: 2 name: #critical sender: sender! !

!SLLog methodsFor: 'logging' stamp: 'gk 8/30/2006 09:04'!
debug: msg

        self emit: msg level: 7 name: #debug sender: nil! !

!SLLog methodsFor: 'logging' stamp: 'gk 8/30/2006 09:05'!
debug: msg sender: sender

        self emit: msg level: 7 name: #debug sender: sender! !

!SLLog methodsFor: 'logging' stamp: 'gk 8/30/2006 09:04'!
emergency: msg

        self emit: msg level: 0 name: #emergency sender: nil! !

!SLLog methodsFor: 'logging' stamp: 'gk 8/30/2006 09:05'!
emergency: msg sender: sender

        self emit: msg level: 0 name: #emergency sender: sender! !

!SLLog methodsFor: 'logging' stamp: 'gk 8/30/2006 09:04'!
error: msg

        self emit: msg level: 3 name: #error sender: nil! !

!SLLog methodsFor: 'logging' stamp: 'gk 8/30/2006 09:05'!
error: msg sender: sender

        self emit: msg level: 3 name: #error sender: sender! !

!SLLog methodsFor: 'logging' stamp: 'gk 8/17/2006 11:48'!
flushEmitters
        emitters do: [:e | e isBlock ifFalse: [(e respondsTo: #flush) ifTrue: 
[e flush]]]! !

!SLLog methodsFor: 'logging' stamp: 'gk 8/30/2006 09:04'!
info: msg

        self emit: msg level: 6 name: #info sender: nil! !

!SLLog methodsFor: 'logging' stamp: 'gk 8/30/2006 09:05'!
info: msg sender: sender

        self emit: msg level: 6 name: #info sender: sender! !

!SLLog methodsFor: 'logging' stamp: 'gk 8/30/2006 09:04'!
notice: msg

        self emit: msg level: 5 name: #notice sender: nil! !

!SLLog methodsFor: 'logging' stamp: 'gk 8/30/2006 09:05'!
notice: msg sender: sender

        self emit: msg level: 5 name: #notice sender: sender! !

!SLLog methodsFor: 'logging' stamp: 'gk 8/30/2006 09:04'!
warn: msg

        self emit: msg level: 4 name: #warn sender: nil! !

!SLLog methodsFor: 'logging' stamp: 'gk 8/30/2006 09:05'!
warn: msg sender: sender

        self emit: msg level: 4 name: #warn sender: sender! !


!SLLog methodsFor: 'private' stamp: 'gk 11/20/2007 22:47'!
emit: msg level: level name: symbol sender: anObject
        "This is the heart of SLLog. Pick out the sender object unless supplied 
and let
        all emitters emit something if it is below the globalMaxLevel. Note the 
guard."

        | sender |
        level <= notifierMaxLevel ifTrue: [super error: symbol].
        level <= globalMaxLevel ifTrue: [
                sender := anObject ifNil: [thisContext sender sender sender 
receiver].
                guard critical: [
                        emitters do: [:blockOrObject |
                                blockOrObject isBlock ifTrue: [
                                        blockOrObject value: level value: 
symbol value: sender value: msg]
                                ifFalse: [
                                        blockOrObject level: level name: symbol 
sender: sender message: msg]]]]! !


!SLLog methodsFor: 'accessing' stamp: 'gk 8/30/2006 09:12'!
globalMaxLevel
        ^globalMaxLevel! !

!SLLog methodsFor: 'accessing' stamp: 'gk 8/30/2006 09:12'!
globalMaxLevel: level
        globalMaxLevel := level! !

!SLLog methodsFor: 'accessing' stamp: 'gk 6/1/2007 11:14'!
notifierMaxLevel
        ^notifierMaxLevel! !

!SLLog methodsFor: 'accessing' stamp: 'gk 6/1/2007 11:14'!
notifierMaxLevel: level
        notifierMaxLevel := level! !

!SLLog methodsFor: 'accessing' stamp: 'gk 8/10/2006 09:05'!
transcriptMaxLevel
        ^transcriptMaxLevel! !

!SLLog methodsFor: 'accessing' stamp: 'gk 8/10/2006 09:05'!
transcriptMaxLevel: level
        transcriptMaxLevel := level! !


!SLLog methodsFor: 'initialize-release' stamp: 'gk 11/21/2007 08:41'!
initialize
        | oldEmitters |
        oldEmitters := emitters.
        
        emitters := Set new.
        transcriptMaxLevel := 7. "We emit everything to Transcript by default"
        globalMaxLevel := 7.    "All messages are emitted"
        notifierMaxLevel := -1. "No messages trigger notifier"
        guard := Monitor new.

        UseTranscript ifTrue: [self addTranscript]. "simple Transcript".

        oldEmitters ifNotNil: [oldEmitters do: [:e | e reInitialize]].
! !


!SLLog methodsFor: 'log protocol' stamp: 'makl 8/11/2006 10:00'!
reInitialize
        "Do nothing"! !

"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!

SLLog class
        instanceVariableNames: ''!

!SLLog class methodsFor: 'emitters' stamp: 'gk 11/21/2007 08:58'!
addMorphic
        self instance addMorphic! !

!SLLog class methodsFor: 'emitters' stamp: 'gk 11/21/2007 01:13'!
addSyslog
        self instance addSyslog! !

!SLLog class methodsFor: 'emitters' stamp: 'gk 11/21/2007 01:01'!
addTranscript
        self instance addTranscript! !

!SLLog class methodsFor: 'emitters' stamp: 'gk 11/21/2007 08:58'!
removeMorphic
        self instance removeMorphic! !

!SLLog class methodsFor: 'emitters' stamp: 'gk 11/21/2007 01:13'!
removeSyslog
        self instance removeSyslog! !

!SLLog class methodsFor: 'emitters' stamp: 'gk 11/21/2007 01:01'!
removeTranscript
        self instance removeTranscript! !


!SLLog class methodsFor: 'logging' stamp: 'gk 3/6/2006 00:34'!
alert: aString
        self instance alert: aString! !

!SLLog class methodsFor: 'logging' stamp: 'gk 8/30/2006 09:05'!
alert: aString sender: aSender
        self instance alert: aString sender: aSender! !

!SLLog class methodsFor: 'logging' stamp: 'gk 3/6/2006 00:34'!
critical: aString
        self instance critical: aString! !

!SLLog class methodsFor: 'logging' stamp: 'gk 8/30/2006 09:05'!
critical: aString sender: aSender
        self instance critical: aString sender: aSender! !

!SLLog class methodsFor: 'logging' stamp: 'gk 3/6/2006 00:34'!
debug: aString
        self instance debug: aString! !

!SLLog class methodsFor: 'logging' stamp: 'gk 8/30/2006 09:06'!
debug: aString sender: aSender
        self instance debug: aString sender: aSender! !

!SLLog class methodsFor: 'logging' stamp: 'gk 3/7/2006 08:25'!
emergency: aString
        self instance emergency: aString! !

!SLLog class methodsFor: 'logging' stamp: 'gk 8/30/2006 09:06'!
emergency: aString sender: aSender
        self instance emergency: aString sender: aSender! !

!SLLog class methodsFor: 'logging' stamp: 'gk 3/6/2006 00:34'!
error: aString
        self instance error: aString! !

!SLLog class methodsFor: 'logging' stamp: 'gk 8/30/2006 09:06'!
error: aString sender: aSender
        self instance error: aString sender: aSender! !

!SLLog class methodsFor: 'logging' stamp: 'gk 8/30/2006 09:06'!
info: aString
        self instance info: aString! !

!SLLog class methodsFor: 'logging' stamp: 'gk 8/30/2006 09:06'!
info: aString sender: aSender
        self instance info: aString sender: aSender! !

!SLLog class methodsFor: 'logging' stamp: 'gk 3/6/2006 00:35'!
notice: aString
        self instance notice: aString! !

!SLLog class methodsFor: 'logging' stamp: 'gk 8/30/2006 09:06'!
notice: aString sender: aSender
        self instance notice: aString sender: aSender! !

!SLLog class methodsFor: 'logging' stamp: 'gk 11/6/2006 09:58'!
time: aBlock debug: aString
        "Shorthand for logging a timed block."

        | ms |
        ms := aBlock timeToRun.
        self instance debug: aString, ': ', ms printString, ' ms'! !

!SLLog class methodsFor: 'logging' stamp: 'gk 3/6/2006 00:33'!
warn: aString
        self instance warn: aString! !

!SLLog class methodsFor: 'logging' stamp: 'gk 8/30/2006 09:06'!
warn: aString sender: aSender
        self instance warn: aString sender: aSender! !


!SLLog class methodsFor: 'logging shorthand' stamp: 'gk 8/30/2006 09:02'!
crap: aString
        "Short hand for typical printf littering.
        All these log entries will end up under a sender called #crap."

        self instance debug: aString sender: #crap! !

!SLLog class methodsFor: 'logging shorthand' stamp: 'gk 8/30/2006 09:00'!
gokr: aString
        "An example of simple personal logging.
        All these log entries will end up under a sender called #gokr
        which makes it easy for other developers to filter it out."

        self instance debug: aString sender: #gokr! !


!SLLog class methodsFor: 'control' stamp: 'gk 8/17/2006 11:46'!
flush

        self instance flushEmitters! !

!SLLog class methodsFor: 'control' stamp: 'gk 8/17/2006 11:50'!
useTranscript: aBoolean

        UseTranscript := aBoolean! !


!SLLog class methodsFor: 'class initialization' stamp: 'gk 6/1/2007 13:19'!
initialize
        "self initialize"

        UseTranscript ifNil: [self useTranscript: true].
        Default := nil.
        self instance initialize! !

!SLLog class methodsFor: 'class initialization' stamp: 'gk 11/20/2007 17:20'!
useGlobalLog: aBoolean
        "Install/deinstall an SLLog in a global called Log."
        
        "self useGlobalLog: true"

        aBoolean
                ifTrue: [Smalltalk at: #Log ifAbsentPut: [self new]]
                ifFalse: [Smalltalk removeKey: #Log]! !


!SLLog class methodsFor: 'instance creation' stamp: 'makl 6/7/2006 10:38'!
instance

        ^Default ifNil: [Default := self new]! !


Object subclass: #SLLogFile
        instanceVariableNames: 'file block uuid'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'SimpleLog'!
!SLLogFile commentStamp: 'gk 11/20/2007 22:39' prior: 0!
This is a simple emitter to a log file on disk to be used with SLLog. See 
#start to see how it is added to the SLLog singleton as an emitter. It handles 
log rotation with a max filesize and number of old logfiles kept around.

        SLLogFile open!


!SLLogFile methodsFor: 'private' stamp: 'makl 6/2/2006 14:16'!
block: aBlock
        block := aBlock! !

!SLLogFile methodsFor: 'private' stamp: 'gk 11/13/2006 15:02'!
deleteOldLogFilesFromNumber: toDelete

        | num dir fn |
        toDelete < 1 ifTrue: [^self].
        num := toDelete.
        dir := file directory.
        [fn := self class baseName, '.',num printString,'.', self class 
extension.
        dir fileExists: fn] whileTrue: [
                dir deleteFileNamed: fn.
                num := num - 1]! !

!SLLogFile methodsFor: 'private' stamp: 'gk 8/17/2006 17:13'!
flush
        file ensureOpen; flush! !

!SLLogFile methodsFor: 'private' stamp: 'gk 8/30/2006 14:11'!
open: fileName
        file := StandardFileStream forceNewFileNamed: fileName.
        file setToEnd.
        self writeLine: '---------------------------'! !

!SLLogFile methodsFor: 'private' stamp: 'gk 8/7/2006 11:13'!
reset
        file reset! !

!SLLogFile methodsFor: 'private' stamp: 'gk 2/16/2007 11:37'!
start
        "Register myself as an emitter."

        SLLog instance addEmitter: self! !

!SLLogFile methodsFor: 'private' stamp: 'gk 2/16/2007 11:37'!
stop
        SLLog instance removeEmitter: self.
        file close.! !

!SLLogFile methodsFor: 'private' stamp: 'gk 8/17/2006 11:44'!
writeLine: aString
        ((aString notNil) and: [file notNil]) ifTrue: [
                file ensureOpen; nextPutAll: aString, String crlf.      
                "file flush"]! !


!SLLogFile methodsFor: 'log protocol' stamp: 'gk 6/1/2007 12:49'!
level: level name: symbol sender: sender message: msg
        "We log everything regardless of level.
        Rotate log file if max size is reached."

        self writeLine: (block value: level value: symbol value: sender value: 
msg).
        file position > self class maxSize ifTrue: [self rotate]! !

!SLLogFile methodsFor: 'log protocol' stamp: 'makl 8/11/2006 10:01'!
reInitialize
        "Do nothing"! !

!SLLogFile methodsFor: 'log protocol' stamp: 'gk 6/1/2007 12:48'!
rotate
        "Close the current one. Maybe delete old log files, keeping last n.
        Rename the current with highest number + 1. Open a new file."

        | dir nextName number |
        file close.
        dir := file directory.
        nextName := dir nextNameFor: self class baseName extension: self class 
extension.
        number := (dir splitNameVersionExtensionFor: nextName) at: 2.
        self deleteOldLogFilesFromNumber: number - self class filesKept.
        dir rename: file name toBe: nextName.
        self open: self class filename
        ! !

"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!

SLLogFile class
        instanceVariableNames: ''!

!SLLogFile class methodsFor: 'settings' stamp: 'gk 2/16/2007 11:19'!
baseName
        ^'squeak'! !

!SLLogFile class methodsFor: 'settings' stamp: 'gk 11/13/2006 14:38'!
extension
        ^'log'! !

!SLLogFile class methodsFor: 'settings' stamp: 'gk 6/1/2007 12:47'!
filesKept

        ^5! !

!SLLogFile class methodsFor: 'settings' stamp: 'gk 11/13/2006 15:03'!
maxSize

        ^10000000 "10 MB"! !


!SLLogFile class methodsFor: 'private' stamp: 'gk 11/13/2006 14:47'!
filename
        ^self baseName, '.', self extension ! !


!SLLogFile class methodsFor: 'instance creation' stamp: 'gk 8/2/2006 09:39'!
named: fileName with: aBlock
        "Create a new log file and start logging with the code in aBlock."

        ^self new open: fileName; block: aBlock; start! !

!SLLogFile class methodsFor: 'instance creation' stamp: 'gk 11/21/2007 01:16'!
open
        "self open"

        self named: FileDirectory default pathName, FileDirectory slash, self 
filename
                with: [:level :symbol :sender :msg |
                        String streamContents: [:stream |
                                DateAndTime now printOn: stream.
                                stream nextPutAll: ' - ', symbol, '(', level 
asString;
                                        nextPutAll: '): ', msg, ' (', sender 
class name, ')']]! !


SystemWindow subclass: #SLLogMorph
        instanceVariableNames: 'classes selectedClass selectedClasses 
selectedSeverity messages selectedMessage removedClasses searchString 
pauseResumeState bufferedMessages'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'SimpleLog'!
!SLLogMorph commentStamp: 'gk 11/21/2007 09:11' prior: 0!
This is a simple but neat log emitter that shows log messages in a Morphic tool 
and offers filtering on level, sender and free text.
Open one up by:

   SLLogMorph openAndRegister!


!SLLogMorph methodsFor: 'accessing' stamp: 'makl 8/29/2006 17:23'!
bufferedMessages
        ^bufferedMessages ifNil: [bufferedMessages := OrderedCollection new]! !

!SLLogMorph methodsFor: 'accessing' stamp: 'makl 8/29/2006 17:20'!
bufferedMessages: anObject
        bufferedMessages := anObject! !

!SLLogMorph methodsFor: 'accessing' stamp: 'makl 8/11/2006 07:51'!
classes
        ^classes ifNil: [classes := SortedCollection new]! !

!SLLogMorph methodsFor: 'accessing' stamp: 'makl 8/10/2006 19:17'!
messages
        ^messages ifNil:[messages := OrderedCollection new]! !

!SLLogMorph methodsFor: 'accessing' stamp: 'makl 8/29/2006 17:08'!
pauseResumeState
        ^pauseResumeState ifNil: [pauseResumeState := false].! !

!SLLogMorph methodsFor: 'accessing' stamp: 'makl 8/29/2006 17:02'!
pauseResumeState: anObject
        pauseResumeState := anObject! !

!SLLogMorph methodsFor: 'accessing' stamp: 'makl 8/11/2006 12:37'!
removedClasses
        ^removedClasses ifNil:[removedClasses := Set new]! !

!SLLogMorph methodsFor: 'accessing' stamp: 'makl 8/21/2006 14:20'!
searchString
        ^searchString ifNil: [searchString := '']! !

!SLLogMorph methodsFor: 'accessing' stamp: 'makl 8/21/2006 14:40'!
searchString: aString
        searchString := aString.
        self changed: #searchString.
        self changed: #filteredMessages! !

!SLLogMorph methodsFor: 'accessing' stamp: 'makl 8/30/2006 09:34'!
selectedClasses
        ^selectedClasses ifNil: [selectedClasses := IdentitySet new]! !

!SLLogMorph methodsFor: 'accessing' stamp: 'makl 8/30/2006 09:16'!
selectedClasses: anObject
        selectedClasses := anObject! !

!SLLogMorph methodsFor: 'accessing' stamp: 'makl 8/30/2006 09:18'!
selectedClassesAt: index
        ^ selectedClasses includes: index! !

!SLLogMorph methodsFor: 'accessing' stamp: 'makl 9/5/2006 10:50'!
selectedClassesAt:index put: value 
        value
                ifTrue: [selectedClasses add: index]
                ifFalse: [selectedClasses
                                remove: index
                                ifAbsent: []].
        self changed: #filteredMessages! !


!SLLogMorph methodsFor: 'menus' stamp: 'makl 8/11/2006 13:06'!
classMenu: aMenu
        "^ aMenu 
                labels: 'Do not log this class
Only log this class'
                lines: #()
                selections: #(removeClass onlyThisClass)"
        ^ aMenu 
                labels: 'Do not log this class'
                lines: #()
                selections: #(removeClass)! !

!SLLogMorph methodsFor: 'menus' stamp: 'makl 8/30/2006 13:20'!
clearMessages
        messages := OrderedCollection new.
        classes := OrderedCollection new.
        selectedClasses := nil.

        self changed: #selectedClass.
        self changed: #classes. 
        self changed: #selectedClasses.
        self changed: #filteredMessages
        ! !

!SLLogMorph methodsFor: 'menus' stamp: 'makl 8/29/2006 16:58'!
clearToThisMessage
        (selectedMessage > 0) ifTrue: [
                messages := self messages copyFrom: selectedMessage to: 
messages size.
                "classes := messages inject: OrderedCollection new into: [:coll 
:msg |
                                        (coll includes: msg key) ifFalse: [coll 
add: msg key]. coll.]."
                self changed: #selectedClass.
                self changed: #classes.
                self changed: #filteredMessages
        ].! !

!SLLogMorph methodsFor: 'menus' stamp: 'makl 8/29/2006 17:10'!
messagesMenu: aMenu
        "^ aMenu 
                labels: 'Do not log this class
Only log this class'
                lines: #()
                selections: #(removeClass onlyThisClass)"
        ^ aMenu 
                labels: 'Clear all messages
Clear all messages before this one
', (self pauseResumeState ifTrue: ['Resume'] ifFalse: ['Pause'])
                lines: #(2)
                selections: #(clearMessages clearToThisMessage pauseResume)! !

!SLLogMorph methodsFor: 'menus' stamp: 'makl 8/11/2006 13:06'!
onlyThisClass
""! !

!SLLogMorph methodsFor: 'menus' stamp: 'gk 6/1/2007 12:55'!
pauseResume
        (self pauseResumeState) ifFalse: [
                self setLabel: 'Paused'.
                pauseResumeState := true
        ] ifTrue: [
                self setLabel: 'LogMorph'.
                pauseResumeState := false.
                messages addAll: self bufferedMessages.
                self bufferedMessages: OrderedCollection new.
                self changed: #classes.
                self changed: #filteredMessages
        ]! !

!SLLogMorph methodsFor: 'menus' stamp: 'makl 8/11/2006 12:33'!
perform: selector orSendTo: otherTarget 
        "Selector was just chosen from a menu by a user. If can respond, then  
        perform it on myself. If not, send it to otherTarget, presumably the  
        editPane from which the menu was invoked."
        (self respondsTo: selector)
                ifTrue: [^ self perform: selector]
                ifFalse: [^ super perform: selector orSendTo: otherTarget]! !

!SLLogMorph methodsFor: 'menus' stamp: 'makl 8/30/2006 13:19'!
removeClass
        selectedClass ifNotNil: [
                self removedClasses add: (selectedClass).
                messages := messages reject: [:e |
                        e key = selectedClass].
                classes remove: selectedClass.
                self selectedClass: 0.
        
                self changed: #selectedClass.
                self changed: #classes.
                self changed: #selectedClasses.
                self changed: #filteredMessages
        ]       ! !


!SLLogMorph methodsFor: 'private' stamp: 'gk 6/1/2007 12:55'!
closeBoxHit
        SLLog instance removeEmitter: self.
        super closeBoxHit! !

!SLLogMorph methodsFor: 'private' stamp: 'makl 9/5/2006 10:40'!
filteredMessages
        ^ self messages inject: OrderedCollection new into: [:coll :each |
                ((self testClass: each) and:
                [self testSeverity: each] and:
                [self testFilter: each])
                ifTrue: [coll add: each value value].
                coll
        ]! !

!SLLogMorph methodsFor: 'private' stamp: 'gk 11/15/2006 10:45'!
getSeverity
        ^#('0-emergency' '1-alert' '2-critical' '3-error' '4-warn' '5-notice' 
'6-info' '7-debug')! !


!SLLogMorph methodsFor: 'log protocol' stamp: 'gk 6/1/2007 12:58'!
level: level name: symbol sender: sender message: msg 
        | str |
        (self removedClasses includes: sender name) ifFalse: [
                str := DateAndTime now printString, ' - ', symbol, ' - ', msg.
                self pauseResumeState ifFalse: [
                        self messages add: (sender name)->(level->str).
                        (self classes anySatisfy: [:any | any = sender name]) 
ifFalse: [
                                classes add: (sender name ifNil: [sender 
printString]).
                                self changed: #classes].
                        self changed: #filteredMessages
                ] ifTrue: [
                        self bufferedMessages add: (sender name)->(level->str)
                ]
        ]! !

!SLLogMorph methodsFor: 'log protocol' stamp: 'gk 2/16/2007 11:37'!
reInitialize
        SLLog instance addEmitter: self! !


!SLLogMorph methodsFor: 'morphic' stamp: 'gk 11/21/2007 08:37'!
open
        "Open an instance in the World, but you need to register it
        as an emitter too."
        
        | horizontalSplit verticalSplit verticalSplit2 |
        horizontalSplit := 0.5.
        verticalSplit := 0.3.
        verticalSplit2 := 0.9.
        selectedClasses := IdentitySet new.
        self addMorph: (
                (PluggableListMorphOfMany
                        on: self
                        list: #classes
                        primarySelection: #selectedClass
                        changePrimarySelection: #selectedClass:
                        listSelection: #selectedClassesAt:
                        changeListSelection: #selectedClassesAt:put:
                        menu: #classMenu:)      
        ) frame: (0@0 corner: horizontalSplit@verticalSplit).
        
        self addMorph: (
                (PluggableListMorph
                        on: self
                        list: #getSeverity
                        selected: #selectedSeverity
                        changeSelected: #selectedSeverity:
                        menu: nil)
                autoDeselect: false) frame: (horizontalSplit@0 corner: 
1@verticalSplit).

        self addMorph: (
                (PluggableListMorph
                        on: self
                        list: #filteredMessages
                        selected: #selectedMessage
                        changeSelected: #selectedMessage:
                        menu: #messagesMenu:)
                autoDeselect: false) frame: (0@verticalSplit corner: 
1@verticalSplit2).

        self addMorph: (
                        (PluggableTextMorph on: self 
                                text: #searchString accept: #searchString:
                                readSelection: nil menu: nil)
                        acceptOnCR: true;
                        setProperty: #alwaysAccept toValue: true;
                        hideScrollBarsIndefinitely )
                frame: (0@verticalSplit2 corner: 1@1).

        self setWindowColor: (Color r: 0.75 g: 0.75 b: 0.05).
        self openInWorld.
        
        ^self! !


!SLLogMorph methodsFor: 'selected' stamp: 'makl 8/11/2006 08:02'!
selectedClass
        ^classes ifNotNil:[classes indexOf: selectedClass] ifNil:[0]! !

!SLLogMorph methodsFor: 'selected' stamp: 'makl 8/11/2006 12:56'!
selectedClass: s
        selectedClass := ((s=0) or: [(classes at: s) = selectedClass])ifTrue: 
[nil] ifFalse: [classes at: s].
        self changed: #selectedClass.
        self changed: #filteredMessages! !

!SLLogMorph methodsFor: 'selected' stamp: 'makl 8/11/2006 08:13'!
selectedMessage
        ^selectedMessage ifNil:[selectedMessage := 0]! !

!SLLogMorph methodsFor: 'selected' stamp: 'makl 8/10/2006 19:00'!
selectedMessage: s
        selectedMessage := (s = selectedMessage) ifTrue: [nil] ifFalse: [s].
        self changed: #selectedMessage! !

!SLLogMorph methodsFor: 'selected' stamp: 'makl 8/14/2006 08:39'!
selectedSeverity
        ^selectedSeverity ifNil:[selectedSeverity := 8]! !

!SLLogMorph methodsFor: 'selected' stamp: 'makl 8/10/2006 19:21'!
selectedSeverity: s
        selectedSeverity := (s = selectedSeverity) ifTrue: [nil] ifFalse: [s].
        self changed: #selectedSeverity.
        self changed: #filteredMessages! !


!SLLogMorph methodsFor: 'testing' stamp: 'makl 9/5/2006 07:38'!
testClass: aMessage
        ^(self selectedClasses anySatisfy: [:c | (classes at: c) = aMessage 
key]) or:
        [self selectedClasses isEmpty]! !

!SLLogMorph methodsFor: 'testing' stamp: 'makl 9/5/2006 10:42'!
testFilter: aMessage
        | lowerString |
        lowerString := aMessage value value asLowercase.
        ^(self searchString asString substrings allSatisfy: [:sub |
                sub := (sub asLowercase) replaceAll: $_ with: (Character space).
                ((sub first = $-) and: [(lowerString includesSubString: sub 
allButFirst) not]) or:
                [(sub first = $+) and: [lowerString includesSubString: sub 
allButFirst]] or:
                [lowerString includesSubString: sub]])! !

!SLLogMorph methodsFor: 'testing' stamp: 'makl 8/29/2006 12:20'!
testSeverity: aMessage
        ^(aMessage value key < self selectedSeverity)! !

"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!

SLLogMorph class
        instanceVariableNames: 'selected'!

!SLLogMorph class methodsFor: 'as yet unclassified' stamp: 'gk 11/21/2007 
08:56'!
open
        "Open in Morphic. Note that this does not register it
        as an emitter in any SLLog instance."

        "self open"

        ^(self labelled: 'LogMorph') open; yourself! !

!SLLogMorph class methodsFor: 'as yet unclassified' stamp: 'gk 11/21/2007 
08:55'!
openAndRegister
        "Open and register in SLLog standard singleton."
        
        "self openAndRegister"

        SLLog instance addMorphic! !


TestCase subclass: #SLLogTests
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'SimpleLog'!
!SLLogTests commentStamp: 'gk 6/1/2007 13:20' prior: 0!
A few simple tests.!


!SLLogTests methodsFor: 'as yet unclassified' stamp: 'gk 6/1/2007 13:30'!
testLogFile
        | levels all some crlf |
        crlf := String with: Character cr with: Character lf.
        levels := {#emergency. #alert. #critical. #error". #warning. #notice. 
#info. #debug"}.
        FileDirectory default deleteFileNamed: 'testall.log'; deleteFileNamed: 
'testsome.log'.
        all := SLLogFile named: 'testall.log' with: [:level :symbol :sender 
:msg |
                        symbol, ': ', msg, ' (', sender class name, ')'].

        some := SLLogFile named: 'testsome.log' with: [:level :symbol :sender 
:msg |
                                ((levels includes: symbol) and: 
                                [msg beginsWith: 'makl'] and:
                                [sender notNil]) ifTrue: [
                                        symbol, ': ', msg, ' (', sender class 
name, ')'
                                ]
                        ].
        "Do some stuff"
        SLLog info: 'Testing info'.
        SLLog warn: 'Testing warning'.
        SLLog error: 'makl cannot code!!'.
        
        all stop.
        some stop.      

        all := (FileDirectory default) oldFileNamed: 'testall.log'.
        some := (FileDirectory default) oldFileNamed: 'testsome.log'.
        self assert: (all contents =    ('---------------------------', crlf,
                                                                'info: Testing 
info (SLLogTests)', crlf, 
                                                                'warn: Testing 
warning (SLLogTests)', crlf, 
                                                                'error: makl 
cannot code!! (SLLogTests)', crlf)).
        self assert: (some contents =   ('---------------------------', crlf,
                                                                'error: makl 
cannot code!! (SLLogTests)', crlf)).
        all close.
        some close.

        FileDirectory default deleteFileNamed: 'testall.log'; deleteFileNamed: 
'testsome.log'.! !


Object subclass: #SLLogWatchdog
        instanceVariableNames: ''
        classVariableNames: 'Buffer CollectMinutes FromEmail SMTPAccount 
SMTPPassword SMTPServer Threshold Timer ToEmail'
        poolDictionaries: ''
        category: 'SimpleLog'!
!SLLogWatchdog commentStamp: 'gk 6/1/2007 13:10' prior: 0!
This log emitter has a threshold. If log messages comes in that are higher than 
the threshold we send an email to an address with the log message. We then set 
a timer and any messages coming in over the threshold until the timer has run 
out we bundle together in another email, send it and set the timer again.

If the timer runs out and there are no buffered messages we are done. This 
approach prevents drowning in emails.

Use like this:

SLLogWatchDog
        fromEmail: '[email protected]';
        toEmail: '[email protected]';
        start

Stop:

SLLogWatchDog stop!


"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!

SLLogWatchdog class
        instanceVariableNames: ''!

!SLLogWatchdog class methodsFor: 'configuration' stamp: 'gk 2/16/2007 11:34'!
SMTPServer: aServerName
        SMTPServer := aServerName ! !

!SLLogWatchdog class methodsFor: 'configuration' stamp: 'gk 6/1/2007 13:13'!
account: anEmail
        SMTPAccount := anEmail ! !

!SLLogWatchdog class methodsFor: 'configuration' stamp: 'gk 2/16/2007 11:46'!
collectMinutes: minutes
        CollectMinutes := minutes ! !

!SLLogWatchdog class methodsFor: 'configuration' stamp: 'gk 6/1/2007 13:05'!
fromEmail: anEmailAddress
        FromEmail := anEmailAddress! !

!SLLogWatchdog class methodsFor: 'configuration' stamp: 'gk 6/1/2007 13:14'!
password: aString
        SMTPPassword := aString! !

!SLLogWatchdog class methodsFor: 'configuration' stamp: 'gk 6/1/2007 13:05'!
toEmail: anEmailAddress
        ToEmail := anEmailAddress! !


!SLLogWatchdog class methodsFor: 'class initialization' stamp: 'gk 6/1/2007 
13:07'!
initialize
        "Default threshold ignores debug and info but includes warnings and 
worse."

        "self initialize"

        Threshold := 4.
        Buffer := Timer := nil.
        CollectMinutes := 300.
        SMTPServer := 'localhost'! !


!SLLogWatchdog class methodsFor: 'log protocol' stamp: 'gk 6/1/2007 13:03'!
level: level name: symbol sender: sender message: msg
        "This method looks messy, and it is - but it is written to be as fast 
as possible."

        "Is this message over the threshold?"
        | message now |
        level <= Threshold
                ifTrue: [
                        message := DateAndTime now printString,' - ', symbol, 
'(', level asString, '): ', msg, ' (', sender class name, ')'.
                        Buffer
                                ifNotNil: [
                                        "Add message to the buffer and possibly 
send it if the timer is out."
                                        Buffer nextPutAll: message.
                                        now := DateAndTime now asSeconds.
                                        "Is the timer out? If so, send the 
buffer."
                                         now > Timer ifTrue: [
                                                self sendBuffer]]
                                ifNil: [
                                        "No buffer to send nor add message to. 
Set the timer and send the message."
                                        Timer := DateAndTime now asSeconds + 
CollectMinutes.
                                        Buffer := String new writeStream.
                                        self sendMessage: message]]
                ifFalse: [
                        "Ok, no message this time - do we have a buffer to send 
when the timer goes out?"
                        Buffer ifNotNil: [
                                now := DateAndTime now asSeconds.
                                "Is the timer out? If so, if the buffer has 
content - send the buffer and reset Timer. Otherwise nil Buffer."
                                now > Timer ifTrue: [
                                        Buffer isEmpty
                                                ifTrue: [Buffer := nil]
                                                ifFalse: [self sendBuffer]]]]! !

!SLLogWatchdog class methodsFor: 'log protocol' stamp: 'gk 11/14/2006 23:17'!
reInitialize

        Buffer := Timer := nil.
        self start! !


!SLLogWatchdog class methodsFor: 'private' stamp: 'gk 6/1/2007 13:14'!
mailFrom: sender to: receivers text: headerAndBody

        | smtpClient |
        smtpClient := SMTPClient new.
        SMTPAccount ifNotNil: [
                smtpClient user: SMTPAccount; password: SMTPPassword.

                [smtpClient openOnHost: (NetNameResolver addressForName: 
SMTPServer timeout: 20) port: 25.
                [smtpClient mailFrom: sender to: receivers text: headerAndBody]
                                ensure: [smtpClient close]]
                                on: Error do: [:ex |
                                        SLLog error: 'Error when sending email: 
', ex asString]
        ]! !

!SLLogWatchdog class methodsFor: 'private' stamp: 'gk 6/1/2007 13:04'!
sendBuffer
        "Send the buffer, reset timer, empty buffer."

        self sendMessage: Buffer contents.
        Timer := DateAndTime now asSeconds + 60.
        Buffer := String new writeStream! !

!SLLogWatchdog class methodsFor: 'private' stamp: 'gk 6/1/2007 13:06'!
sendMessage: msg
        "Send the message."

"Transcript show: 'Watchdog sending: ', msg;cr."
        ToEmail isEmptyOrNil
                ifFalse: [
                        self
                                mailFrom: FromEmail
                                to: (Array with: ToEmail)
                                text: 'Subject: Important log messages

Log messages above threshold:
', msg]
! !


!SLLogWatchdog class methodsFor: 'start and stop' stamp: 'gk 6/1/2007 13:08'!
start
        "self start"
        
        SLLog instance addEmitter: self! !

!SLLogWatchdog class methodsFor: 'start and stop' stamp: 'gk 6/1/2007 13:08'!
stop
        "self stop"
        
        SLLog instance removeEmitter: self! !


Object subclass: #SLSyslogSender
        instanceVariableNames: 'hostName facility hosts'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'SimpleLog'!
!SLSyslogSender commentStamp: 'gk 11/21/2007 01:31' prior: 0!
An emitter that emits to a remote host (or several) using the syslog protocol 
in UDP.
The facility is set by number, default is local0, probably just fine.

You may want to set the hostName to something different than localhost.

- Make sure your syslog host (default is localhost) is enabled to listen on 
port 514.

Test this by:

        SLLog addSyslog.  "Adds a syslog sender emitter to localhost by default"
        SLLog info: 'Testing syslog'!


!SLSyslogSender methodsFor: 'accessing' stamp: 'gk 11/21/2007 01:10'!
addHost: aString
        "Add a host to send to, 514 is default syslog port."

        self addHost: aString port: 514! !

!SLSyslogSender methodsFor: 'accessing' stamp: 'gk 11/21/2007 01:10'!
addHost: aString port: aPortNumber
        "Add a host name with portnumber."

        hosts add: aString -> (aPortNumber ifNil: [514])! !

!SLSyslogSender methodsFor: 'accessing' stamp: 'gk 11/21/2007 01:07'!
facility: anInteger
        "We keep it multiplied with 8 so we don't need to do that every time we 
emit."

        facility := anInteger * 8! !

!SLSyslogSender methodsFor: 'accessing' stamp: 'gk 11/21/2007 00:03'!
hostName
        "Apparently this is not that easy to find cross platform."

        ^hostName ifNil: ['localhost']! !

!SLSyslogSender methodsFor: 'accessing' stamp: 'gk 11/21/2007 01:08'!
hostName: aHostName
        "Apparently this is not that easy to find cross platform."

        hostName := aHostName! !


!SLSyslogSender methodsFor: 'initialize-release' stamp: 'gk 11/21/2007 01:12'!
initialize
        self facility: 16. "Use 1 (=user level) or 16-23 (=local use local0-7)"
        hosts := OrderedCollection with: 'localhost'->514! !


!SLSyslogSender methodsFor: 'private' stamp: 'gk 11/21/2007 01:29'!
level: level name: symbol sender: sender message: msg
        "Produce a standard message and send it using UDP to all registered 
hosts."

        | message now d dd |
        now := DateAndTime now.
        d := now dayOfMonth.
        dd := d < 10 ifTrue: [' ', d asString] ifFalse: [d asString].
        message := String streamContents: [:s |
                s nextPut: $<;
                        nextPutAll: (facility + level) asString;
                        nextPut: $>;
                        nextPutAll: now monthAbbreviation;
                        nextPutAll: ' ', dd, ' '.
                now asTime print24: true on: s.
                s nextPutAll: ' ', self hostName, ' ';
                        nextPutAll: symbol, '(', level asString, '): ', msg, ' 
(', sender asString, ')'].

        hosts do: [:host | self send: message to: host key port: host value]! !

!SLSyslogSender methodsFor: 'private' stamp: 'gk 11/21/2007 00:04'!
send: message to: host port: port
        "Send formatted UDP message to host and port."

        | sock |
        sock := Socket newUDP.
        sock setPeer: NetNameResolver localHostAddress port: port.
        sock sendData: message.
        sock waitForSendDoneFor: Socket standardDeadline.
        sock close! !

SLLog initialize!
SLLogWatchdog initialize!

Reply via email to