├── src ├── .properties ├── TinyLogger │ ├── package.st │ ├── FullBlockClosure.extension.st │ ├── Object.extension.st │ ├── TinyCurrentLogger.class.st │ ├── ManifestTinyLogger.class.st │ ├── TinyOpenTerminalVisitor.class.st │ ├── TinyTranscriptLogger.class.st │ ├── TinyStdoutLogger.class.st │ ├── TinyAbstractLogger.class.st │ ├── TinyFileLogger.class.st │ ├── TinyLeafLogger.class.st │ └── TinyLogger.class.st ├── TinyLogger-Tests │ ├── package.st │ ├── ManifestTinyLoggerTests.class.st │ ├── TinyAbstractLoggerTest.class.st │ ├── TinyStdoutLoggerTest.class.st │ ├── TinyLeafLoggerTest.class.st │ ├── TinyFileLoggerTest.class.st │ └── TinyLoggerTest.class.st └── BaselineOfTinyLogger │ ├── package.st │ ├── ManifestBaselineOfTinyLogger.class.st │ └── BaselineOfTinyLogger.class.st ├── .gitattributes ├── .project ├── documentation ├── uml.png ├── v1toV2.md ├── DevelopmentGuide.md ├── uml.uxf └── UserGuide.md ├── .smalltalk.ston ├── .gitignore ├── .github └── workflows │ └── continuous.yml ├── scripts └── mutationTesting.st ├── LICENSE ├── README.md └── CHANGELOG.md /src/.properties: -------------------------------------------------------------------------------- 1 | { 2 | #format : #tonel 3 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | **/*.st linguist-language=Smalltalk -------------------------------------------------------------------------------- /src/TinyLogger/package.st: -------------------------------------------------------------------------------- 1 | Package { #name : 'TinyLogger' } 2 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | { 2 | 'srcDirectory' : 'src', 3 | 'tags' : [ #Jecisc ] 4 | } -------------------------------------------------------------------------------- /src/TinyLogger-Tests/package.st: -------------------------------------------------------------------------------- 1 | Package { #name : 'TinyLogger-Tests' } 2 | -------------------------------------------------------------------------------- /src/BaselineOfTinyLogger/package.st: -------------------------------------------------------------------------------- 1 | Package { #name : 'BaselineOfTinyLogger' } 2 | -------------------------------------------------------------------------------- /documentation/uml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jecisc/TinyLogger/HEAD/documentation/uml.png -------------------------------------------------------------------------------- /src/TinyLogger/FullBlockClosure.extension.st: -------------------------------------------------------------------------------- 1 | Extension { #name : 'FullBlockClosure' } 2 | 3 | { #category : '*TinyLogger' } 4 | FullBlockClosure >> record [ 5 | "See comment of TinyLogger>>#record:" 6 | 7 | self record: self 8 | ] 9 | -------------------------------------------------------------------------------- /.smalltalk.ston: -------------------------------------------------------------------------------- 1 | SmalltalkCISpec { 2 | #loading : [ 3 | SCIMetacelloLoadSpec { 4 | #baseline : 'TinyLogger', 5 | #directory : 'src' 6 | } 7 | ], 8 | #testing : { 9 | #coverage : { 10 | #packages : [ 'TinyLogger' ] 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # changes file 2 | *.changes 3 | 4 | # system image 5 | *.image 6 | 7 | # Pharo Smalltalk Debug log file 8 | PharoDebug.log 9 | 10 | # Squeak Smalltalk Debug log file 11 | SqueakDebug.log 12 | 13 | # Monticello package cache 14 | /package-cache 15 | 16 | # playground cache 17 | /play-cache 18 | /play-stash 19 | 20 | # Metacello-github cache 21 | /github-cache 22 | github-*.zip 23 | 24 | **/.DS_STORE -------------------------------------------------------------------------------- /src/TinyLogger/Object.extension.st: -------------------------------------------------------------------------------- 1 | Extension { #name : 'Object' } 2 | 3 | { #category : '*TinyLogger' } 4 | Object >> execute: aBlock recordedAs: aBlockOrString [ 5 | ^ TinyCurrentLogger value execute: aBlock recordedAs: aBlockOrString 6 | ] 7 | 8 | { #category : '*TinyLogger' } 9 | Object >> record [ 10 | self record: self asString 11 | ] 12 | 13 | { #category : '*TinyLogger' } 14 | Object >> record: aBlockOrString [ 15 | TinyCurrentLogger value record: aBlockOrString 16 | ] 17 | -------------------------------------------------------------------------------- /src/TinyLogger-Tests/ManifestTinyLoggerTests.class.st: -------------------------------------------------------------------------------- 1 | " 2 | I contains tests of the implementation of a simple and tiny logger. 3 | 4 | I implement different kind of loggers such as: 5 | - Transcript 6 | - Stdout 7 | - File 8 | 9 | Check the class comment of `TinyLogger` or the documentation present on https://github.com/jecisc/TinyLogger repository for more informations. 10 | " 11 | Class { 12 | #name : 'ManifestTinyLoggerTests', 13 | #superclass : 'PackageManifest', 14 | #category : 'TinyLogger-Tests-Manifest', 15 | #package : 'TinyLogger-Tests', 16 | #tag : 'Manifest' 17 | } 18 | -------------------------------------------------------------------------------- /src/BaselineOfTinyLogger/ManifestBaselineOfTinyLogger.class.st: -------------------------------------------------------------------------------- 1 | " 2 | I contains the implementation of a simple and tiny logger. 3 | 4 | I implement different kind of loggers such as: 5 | - Transcript 6 | - Stdout 7 | - File 8 | 9 | Check the class comment of `TinyLogger` or the documentation present on https://github.com/jecisc/TinyLogger repository for more informations. 10 | " 11 | Class { 12 | #name : 'ManifestBaselineOfTinyLogger', 13 | #superclass : 'PackageManifest', 14 | #category : 'BaselineOfTinyLogger-Manifest', 15 | #package : 'BaselineOfTinyLogger', 16 | #tag : 'Manifest' 17 | } 18 | -------------------------------------------------------------------------------- /src/TinyLogger/TinyCurrentLogger.class.st: -------------------------------------------------------------------------------- 1 | " 2 | Description 3 | -------------------- 4 | 5 | I am a dynamic variable defining the current `TinyLogger` to use to record events. 6 | 7 | Examples 8 | -------------------- 9 | 10 | TinyCurrentLogger value: (TinyLogger new addTranscriptLogger; yourself) during: [ 11 | 'test' record. 12 | TinyCurrentLogger value record: 'Test2' 13 | ] 14 | " 15 | Class { 16 | #name : 'TinyCurrentLogger', 17 | #superclass : 'DynamicVariable', 18 | #category : 'TinyLogger-Core', 19 | #package : 'TinyLogger', 20 | #tag : 'Core' 21 | } 22 | 23 | { #category : 'accessing' } 24 | TinyCurrentLogger >> default [ 25 | ^ TinyLogger default 26 | ] 27 | -------------------------------------------------------------------------------- /.github/workflows/continuous.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | env: 9 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 10 | strategy: 11 | matrix: 12 | smalltalk: [ Pharo64-7.0, Pharo64-8.0, Pharo64-9.0, Pharo64-10, Pharo64-11, Pharo64-12, Pharo64-13, Pharo64-alpha ] 13 | name: ${{ matrix.smalltalk }} 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: hpi-swa/setup-smalltalkCI@v1 17 | with: 18 | smalltalk-image: ${{ matrix.smalltalk }} 19 | - run: smalltalkci -s ${{ matrix.smalltalk }} 20 | shell: bash 21 | timeout-minutes: 15 -------------------------------------------------------------------------------- /src/TinyLogger/ManifestTinyLogger.class.st: -------------------------------------------------------------------------------- 1 | " 2 | I contains the implementation of a simple and tiny logger. 3 | 4 | I implement different kind of loggers such as: 5 | - Transcript 6 | - Stdout 7 | - File 8 | 9 | Check the class comment of `TinyLogger` or the documentation present on https://github.com/jecisc/TinyLogger repository for more informations. 10 | " 11 | Class { 12 | #name : 'ManifestTinyLogger', 13 | #superclass : 'PackageManifest', 14 | #category : 'TinyLogger-Manifest', 15 | #package : 'TinyLogger', 16 | #tag : 'Manifest' 17 | } 18 | 19 | { #category : 'code-critics' } 20 | ManifestTinyLogger class >> ruleTempsReadBeforeWrittenRuleV1FalsePositive [ 21 | ^ #(#(#(#RGMethodDefinition #(#TinyLogger #execute:recordedAs: #false)) #'2022-06-02T15:28:59.459+02:00') ) 22 | ] 23 | -------------------------------------------------------------------------------- /src/TinyLogger/TinyOpenTerminalVisitor.class.st: -------------------------------------------------------------------------------- 1 | Class { 2 | #name : 'TinyOpenTerminalVisitor', 3 | #superclass : 'OSPlatformVisitor', 4 | #instVars : [ 5 | 'logger' 6 | ], 7 | #category : 'TinyLogger-System', 8 | #package : 'TinyLogger', 9 | #tag : 'System' 10 | } 11 | 12 | { #category : 'as yet unclassified' } 13 | TinyOpenTerminalVisitor class >> openTerminalForFileLogger: aTinyFileLogger [ 14 | 15 | ^ self new 16 | logger: aTinyFileLogger; 17 | visit 18 | ] 19 | 20 | { #category : 'accessing' } 21 | TinyOpenTerminalVisitor >> logger [ 22 | 23 | ^ logger 24 | ] 25 | 26 | { #category : 'accessing' } 27 | TinyOpenTerminalVisitor >> logger: anObject [ 28 | 29 | logger := anObject 30 | ] 31 | 32 | { #category : 'accessing' } 33 | TinyOpenTerminalVisitor >> visitWindows: aPlatform [ 34 | 35 | LibC runCommand: 36 | 'start powershell -noexit -noprofil -command "& { Get-Content -wait ' 37 | , self logger fileReference pathString , ' }"' 38 | ] 39 | -------------------------------------------------------------------------------- /scripts/mutationTesting.st: -------------------------------------------------------------------------------- 1 | Metacello new 2 | githubUser: 'jecisc' project: 'TinyLogger' commitish: 'v1.x.x' path: 'src'; 3 | baseline: 'TinyLogger'; 4 | load. 5 | 6 | Metacello new 7 | baseline: 'MuTalk'; 8 | repository: 'github://pavel-krivanek/mutalk/src'; 9 | load. 10 | 11 | analysis := MutationTestingAnalysis 12 | testCasesFrom: 'TinyLogger-Tests' asPackage definedClasses 13 | mutating: 'TinyLogger' asPackage definedClasses 14 | using: MutantOperator contents 15 | with: AllTestsMethodsRunningMutantEvaluationStrategy new. 16 | analysis run. 17 | alive := analysis generalResult aliveMutants. 18 | 19 | browser := GLMTabulator new. 20 | browser 21 | row: #results; 22 | row: #diff. 23 | browser transmit to: #results. 24 | browser transmit to: #diff; from: #results; andShow: [ :a | 25 | a diff display: [ :mutant | {((RBParser parseMethod: (mutant mutant originalSource)) formattedCode) . ((RBParser parseMethod: (mutant mutant modifiedSource)) formattedCode)}] ]. 26 | browser openOn: alive. -------------------------------------------------------------------------------- /src/TinyLogger/TinyTranscriptLogger.class.st: -------------------------------------------------------------------------------- 1 | " 2 | Description 3 | -------------------- 4 | 5 | I am a logger that will log the informations in the Transcript. 6 | 7 | I should not be used directly but via a `TinyLogger`. 8 | 9 | Examples 10 | -------------------- 11 | 12 | (TinyTranscriptLogger for: aTinyLogger) record: 'First log' 13 | " 14 | Class { 15 | #name : 'TinyTranscriptLogger', 16 | #superclass : 'TinyLeafLogger', 17 | #category : 'TinyLogger-Core', 18 | #package : 'TinyLogger', 19 | #tag : 'Core' 20 | } 21 | 22 | { #category : 'accessing' } 23 | TinyTranscriptLogger class >> kind [ 24 | 25 | ^ 'transcript' 26 | ] 27 | 28 | { #category : 'logging' } 29 | TinyTranscriptLogger >> clearLog [ 30 | Transcript clear 31 | ] 32 | 33 | { #category : 'opening' } 34 | TinyTranscriptLogger >> open [ 35 | Transcript open 36 | ] 37 | 38 | { #category : 'logging' } 39 | TinyTranscriptLogger >> record: aString [ 40 | Transcript trace: (String streamContents: [ :s | self record: aString on: s ]) 41 | ] 42 | -------------------------------------------------------------------------------- /src/BaselineOfTinyLogger/BaselineOfTinyLogger.class.st: -------------------------------------------------------------------------------- 1 | " 2 | Description 3 | -------------------- 4 | 5 | I am a baseline to configure the Tiny logger project. 6 | 7 | https://github.com/jecisc/TinyLogger 8 | " 9 | Class { 10 | #name : 'BaselineOfTinyLogger', 11 | #superclass : 'BaselineOf', 12 | #category : 'BaselineOfTinyLogger', 13 | #package : 'BaselineOfTinyLogger' 14 | } 15 | 16 | { #category : 'baselines' } 17 | BaselineOfTinyLogger >> baseline: spec [ 18 | 19 | spec 20 | for: #common 21 | do: [ 22 | "Dependencies" 23 | self mocketry: spec. 24 | 25 | "Packages" 26 | spec 27 | package: 'TinyLogger'; 28 | package: 'TinyLogger-Tests' with: [ spec requires: #('TinyLogger' 'Mocketry') ]. 29 | 30 | "Groups" 31 | spec 32 | group: 'Core' with: #('TinyLogger'); 33 | group: 'Tests' with: #('TinyLogger-Tests') ] 34 | ] 35 | 36 | { #category : 'dependencies' } 37 | BaselineOfTinyLogger >> mocketry: spec [ 38 | spec 39 | baseline: 'Mocketry' 40 | with: [ spec 41 | loads: #('Core'); 42 | repository: 'github://dionisiydk/Mocketry:v7.0.2' ] 43 | ] 44 | -------------------------------------------------------------------------------- /src/TinyLogger-Tests/TinyAbstractLoggerTest.class.st: -------------------------------------------------------------------------------- 1 | " 2 | A TinyAbstractLoggerTest is a test class for testing the behavior of TinyAbstractLogger 3 | " 4 | Class { 5 | #name : 'TinyAbstractLoggerTest', 6 | #superclass : 'TestCase', 7 | #instVars : [ 8 | 'logger' 9 | ], 10 | #category : 'TinyLogger-Tests-Core', 11 | #package : 'TinyLogger-Tests', 12 | #tag : 'Core' 13 | } 14 | 15 | { #category : 'testing' } 16 | TinyAbstractLoggerTest class >> isAbstract [ 17 | ^ self = TinyAbstractLoggerTest 18 | ] 19 | 20 | { #category : 'testing' } 21 | TinyAbstractLoggerTest class >> shouldInheritSelectors [ 22 | ^ true 23 | ] 24 | 25 | { #category : 'helpers' } 26 | TinyAbstractLoggerTest >> actualClass [ 27 | ^ self subclassResponsibility 28 | ] 29 | 30 | { #category : 'tests' } 31 | TinyAbstractLoggerTest >> testClearLog [ 32 | self subclassResponsibility 33 | ] 34 | 35 | { #category : 'tests' } 36 | TinyAbstractLoggerTest >> testIndentationString [ 37 | self subclassResponsibility 38 | ] 39 | 40 | { #category : 'tests' } 41 | TinyAbstractLoggerTest >> testRecord [ 42 | self subclassResponsibility 43 | ] 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 CyrilFerlicot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/TinyLogger/TinyStdoutLogger.class.st: -------------------------------------------------------------------------------- 1 | " 2 | Description 3 | -------------------- 4 | 5 | I am a logger that will log the informations in the StdOut. 6 | 7 | I should not be used directly but via a `TinyLogger`. 8 | 9 | Examples 10 | -------------------- 11 | 12 | (TinyStdoutLogger for: aTinyLogger) record: 'First log' 13 | " 14 | Class { 15 | #name : 'TinyStdoutLogger', 16 | #superclass : 'TinyLeafLogger', 17 | #category : 'TinyLogger-Core', 18 | #package : 'TinyLogger', 19 | #tag : 'Core' 20 | } 21 | 22 | { #category : 'accessing' } 23 | TinyStdoutLogger class >> kind [ 24 | 25 | ^ 'stdout' 26 | ] 27 | 28 | { #category : 'logging' } 29 | TinyStdoutLogger >> clearLog [ 30 | "We do nothing here since we cannot clear stdout" 31 | 32 | 33 | ] 34 | 35 | { #category : 'opening' } 36 | TinyStdoutLogger >> open [ 37 | 38 | self inform: 'Cannot open STDOut logger.' 39 | ] 40 | 41 | { #category : 'logging' } 42 | TinyStdoutLogger >> record: aString [ 43 | | stream | 44 | stream := Stdio stdout. 45 | self record: aString on: stream. 46 | 47 | "The flush is needed to send the record in the stdout." 48 | stream flush 49 | ] 50 | -------------------------------------------------------------------------------- /src/TinyLogger-Tests/TinyStdoutLoggerTest.class.st: -------------------------------------------------------------------------------- 1 | " 2 | A TinyStdoutLoggerTest is a test class for testing the behavior of TinyStdoutLogger 3 | " 4 | Class { 5 | #name : 'TinyStdoutLoggerTest', 6 | #superclass : 'TinyLeafLoggerTest', 7 | #category : 'TinyLogger-Tests-Core', 8 | #package : 'TinyLogger-Tests', 9 | #tag : 'Core' 10 | } 11 | 12 | { #category : 'helpers' } 13 | TinyStdoutLoggerTest >> actualClass [ 14 | ^ TinyStdoutLogger 15 | ] 16 | 17 | { #category : 'tests' } 18 | TinyStdoutLoggerTest >> testClearLog [ 19 | self shouldnt: [ logger clearLog ] raise: Error 20 | ] 21 | 22 | { #category : 'tests' } 23 | TinyStdoutLoggerTest >> testIndentationString [ 24 | 25 | | stream | 26 | stream := '' writeStream. 27 | [ 28 | Stdio stub stdout willReturn: stream. 29 | parentLogger indentationString: '--'. 30 | logger execute: [ logger record: 'This is a test' ] recordedAs: 'block'. 31 | self assert: (stream contents asString lines second includesSubstring: '--This is a test') ] ensure: [ 32 | Stdio recoverFromGHMutation. 33 | stream close ] 34 | ] 35 | 36 | { #category : 'tests' } 37 | TinyStdoutLoggerTest >> testRecord [ 38 | | stream | 39 | stream := '' writeStream. 40 | [ Stdio stub stdout willReturn: stream. 41 | logger record: 'This is a test'. 42 | self assert: (stream contents asString lines last includesSubstring: 'This is a test') ] 43 | ensure: [ Stdio recoverFromGHMutation. 44 | stream close ] 45 | ] 46 | -------------------------------------------------------------------------------- /src/TinyLogger-Tests/TinyLeafLoggerTest.class.st: -------------------------------------------------------------------------------- 1 | " 2 | A TinyConcreteLoggerTest is a test class for testing the behavior of TinyConcreteLogger 3 | " 4 | Class { 5 | #name : 'TinyLeafLoggerTest', 6 | #superclass : 'TinyAbstractLoggerTest', 7 | #instVars : [ 8 | 'parentLogger' 9 | ], 10 | #category : 'TinyLogger-Tests-Core', 11 | #package : 'TinyLogger-Tests', 12 | #tag : 'Core' 13 | } 14 | 15 | { #category : 'testing' } 16 | TinyLeafLoggerTest class >> isAbstract [ 17 | ^ self = TinyLeafLoggerTest 18 | ] 19 | 20 | { #category : 'private' } 21 | TinyLeafLoggerTest >> performTest [ 22 | 23 | TinyCurrentLogger value: parentLogger during: [ super performTest ] 24 | ] 25 | 26 | { #category : 'running' } 27 | TinyLeafLoggerTest >> setUp [ 28 | super setUp. 29 | parentLogger := TinyLogger new. 30 | logger := self actualClass for: parentLogger 31 | ] 32 | 33 | { #category : 'tests' } 34 | TinyLeafLoggerTest >> testRecordPreambleOn [ 35 | parentLogger timestampFormatBlock: [ :s | s << 'this is a test' ]. 36 | self assert: (String streamContents: [ :s | logger recordPreambleOn: s]) equals: 'this is a test: '. 37 | parentLogger timestampFormatBlock: [ :s | s << 'this is a test2' ]. 38 | self assert: (String streamContents: [ :s | logger recordPreambleOn: s]) equals: 'this is a test2: ' 39 | ] 40 | 41 | { #category : 'tests' } 42 | TinyLeafLoggerTest >> testRecordTimestampOn [ 43 | 44 | parentLogger timestampFormatBlock: [ :s | s << 'this is a test' ]. 45 | self assert: (String streamContents: [ :s | logger recordTimestampOn: s ]) equals: 'this is a test'. 46 | parentLogger timestampFormatBlock: [ :s | s << 'this is a test2' ]. 47 | self assert: (String streamContents: [ :s | logger recordTimestampOn: s ]) equals: 'this is a test2' 48 | ] 49 | -------------------------------------------------------------------------------- /src/TinyLogger/TinyAbstractLogger.class.st: -------------------------------------------------------------------------------- 1 | " 2 | Description 3 | -------------------- 4 | 5 | I am an abstract logger to define the minimal behavior all my subclasses should define. 6 | 7 | The user should mostly work with the `TinyLogger` class. This class is the main entry point of the project. 8 | 9 | I follow a composite design pattern. I am subclassed by a `TinyLogger` that is a composite of leaf loggers, and `TinyLeafLogger` defining the behavior of a concrete logger. 10 | 11 | Users should configure a `TinyLogger` with the concrete loggers they want and use this one that will dispatch the logging behavior to the other loggers. 12 | 13 | Public API and Key Messages 14 | -------------------- 15 | 16 | - #record: Takes a string and record it. 17 | - #<< Alias of #record:. 18 | - #clearLog Clear the output of the logger. Will have different effect depending on the logger. 19 | 20 | " 21 | Class { 22 | #name : 'TinyAbstractLogger', 23 | #superclass : 'Object', 24 | #category : 'TinyLogger-Core', 25 | #package : 'TinyLogger', 26 | #tag : 'Core' 27 | } 28 | 29 | { #category : 'testing' } 30 | TinyAbstractLogger class >> isAbstract [ 31 | 32 | ^ self = TinyAbstractLogger 33 | ] 34 | 35 | { #category : 'logging' } 36 | TinyAbstractLogger >> << aString [ 37 | self record: aString 38 | ] 39 | 40 | { #category : 'logging' } 41 | TinyAbstractLogger >> clearLog [ 42 | "Whe called, this method should clear the previous logs" 43 | 44 | self subclassResponsibility 45 | ] 46 | 47 | { #category : 'accessing' } 48 | TinyAbstractLogger >> indentExecutionBlock [ 49 | 50 | 51 | ^ self subclassResponsibility 52 | ] 53 | 54 | { #category : 'accessing' } 55 | TinyAbstractLogger >> indentationString [ 56 | 57 | 58 | ^ self subclassResponsibility 59 | ] 60 | 61 | { #category : 'logging' } 62 | TinyAbstractLogger >> record: aString [ 63 | self subclassResponsibility 64 | ] 65 | 66 | { #category : 'accessing' } 67 | TinyAbstractLogger >> timestampFormatBlock [ 68 | 69 | ^ self subclassResponsibility 70 | ] 71 | -------------------------------------------------------------------------------- /documentation/v1toV2.md: -------------------------------------------------------------------------------- 1 | # Migration guide v1 to v2 2 | 3 | Between the v1 and v2 some things changed. 4 | 5 | ## Drop P6 compatibility 6 | 7 | V2 does not work anymore with Pharo 6 and 6.1. If your project is on Pharo 6, keep using the version 1 of TinyLogger. 8 | 9 | ## Cannot add a logger created from #for: 10 | 11 | In the past it was possible to do: 12 | 13 | ```Smalltalk 14 | TinyLogger default addLogger: (TinyTranscriptLogger for: TinyLogger default) 15 | ``` 16 | 17 | Now this does not work anymore because #`for:` is now adding itself to the parameter. If you still do this, yu will end up with the logger duplicated. 18 | 19 | You can now do: 20 | 21 | ```Smalltalk 22 | TinyLogger default addLogger: TinyTranscriptLogger new 23 | ``` 24 | 25 | or: 26 | 27 | ```Smalltalk 28 | TinyLogger default addTrascriptLogger "or ensureTranscriptLogger" 29 | ``` 30 | 31 | ## Removal of the leading space before colon 32 | 33 | In v1 the timestamp was followed by a space before the colon. 34 | 35 | Like this: 36 | 37 | ``` 38 | 2018-11-29T23:19:55.511775+01:00 : This is a string to log 39 | ``` 40 | 41 | In the v2 this was removed. If you want this space, you can add it the the #`timestampFormatBlock:`. 42 | 43 | ```Smalltalk 44 | TinyLogger default timestampFormatBlock: [ :aStream :timestamp | aStream << timestamp asString << ' ' ] 45 | ``` 46 | 47 | 48 | ## Option execution block indentation 49 | 50 | In the past, using `#execute:recordedAs:` was adding some extra indentation like this: 51 | 52 | ```Smalltalk 53 | self execute: [ 'Log' record ] recordedAs: 'Task' 54 | ``` 55 | 56 | ``` 57 | 2018-11-29T23:21:04.897775+01:00: Begin: Task 58 | 2018-11-29T23:21:04.900775+01:00: Log 59 | 2018-11-29T23:21:04.909775+01:00: End: Task 60 | ``` 61 | 62 | Now it prints like this: 63 | 64 | ``` 65 | 2018-11-29T23:21:04.897775+01:00: Begin: Task 66 | 2018-11-29T23:21:04.900775+01:00: Log 67 | 2018-11-29T23:21:04.909775+01:00: End: Task 68 | ``` 69 | 70 | In order to get the previous behavior you can use `#indentExecutionBlock:` like this: 71 | 72 | ```Smalltalk 73 | TinyLogger default indentExecutionBlock: true 74 | ``` 75 | -------------------------------------------------------------------------------- /src/TinyLogger/TinyFileLogger.class.st: -------------------------------------------------------------------------------- 1 | " 2 | Description 3 | -------------------- 4 | 5 | I am a logger that will log the informations in a file on the file system. 6 | 7 | I am configured with a file name. If no file name is given to me, I'll use `TinyLogger.log` by default. 8 | 9 | I should not be used directly but via a `TinyLogger`. 10 | 11 | Examples 12 | -------------------- 13 | 14 | (TinyFileLogger for: aTinyLogger) record: 'First log'. 15 | 16 | (TinyFileLogger for: aTinyLogger named: 'anotherFile.log') record: 'First log'. 17 | 18 | Internal Representation and Key Implementation Points. 19 | -------------------- 20 | 21 | Instance Variables 22 | fileName: Name of the file in which informations should be logged. 23 | 24 | " 25 | Class { 26 | #name : 'TinyFileLogger', 27 | #superclass : 'TinyLeafLogger', 28 | #instVars : [ 29 | 'fileName' 30 | ], 31 | #category : 'TinyLogger-Core', 32 | #package : 'TinyLogger', 33 | #tag : 'Core' 34 | } 35 | 36 | { #category : 'accessing' } 37 | TinyFileLogger class >> defaultFileName [ 38 | ^ 'TinyLogger.log' 39 | ] 40 | 41 | { #category : 'instance creation' } 42 | TinyFileLogger class >> for: aLogger named: aString [ 43 | ^ (self for: aLogger) 44 | fileName: aString; 45 | yourself 46 | ] 47 | 48 | { #category : 'accessing' } 49 | TinyFileLogger class >> kind [ 50 | 51 | ^ 'file' 52 | ] 53 | 54 | { #category : 'instance creation' } 55 | TinyFileLogger class >> named: aString [ 56 | ^ self new 57 | fileName: aString; 58 | yourself 59 | ] 60 | 61 | { #category : 'logging' } 62 | TinyFileLogger >> clearLog [ 63 | self fileReference ensureDelete 64 | ] 65 | 66 | { #category : 'accessing' } 67 | TinyFileLogger >> defaultFileName [ 68 | ^ self class defaultFileName 69 | ] 70 | 71 | { #category : 'accessing' } 72 | TinyFileLogger >> fileName [ 73 | ^ fileName ifNil: [ fileName := self defaultFileName ] 74 | ] 75 | 76 | { #category : 'accessing' } 77 | TinyFileLogger >> fileName: anObject [ 78 | fileName := anObject 79 | ] 80 | 81 | { #category : 'accessing' } 82 | TinyFileLogger >> fileReference [ 83 | ^ self fileName asFileReference 84 | ] 85 | 86 | { #category : 'accessing' } 87 | TinyFileLogger >> fileStreamDo: aBlock [ 88 | "For now we re-open the file all the time to avoid problems when we change its image of computer/OS or if the user delete the file. Maybe later we can find a better solution but in any case we should test such weird behaviors." 89 | 90 | self fileReference 91 | ensureCreateFile; 92 | writeStreamDo: [ :s | 93 | s setToEnd. 94 | aBlock value: s ] 95 | ] 96 | 97 | { #category : 'opening' } 98 | TinyFileLogger >> open [ 99 | TinyOpenTerminalVisitor openTerminalForFileLogger: self 100 | ] 101 | 102 | { #category : 'logging' } 103 | TinyFileLogger >> record: aString [ 104 | self fileStreamDo: [ :aStream | self record: aString on: aStream ] 105 | ] 106 | -------------------------------------------------------------------------------- /src/TinyLogger-Tests/TinyFileLoggerTest.class.st: -------------------------------------------------------------------------------- 1 | " 2 | A TinyFileLoggerTest is a test class for testing the behavior of TinyFileLogger 3 | " 4 | Class { 5 | #name : 'TinyFileLoggerTest', 6 | #superclass : 'TinyLeafLoggerTest', 7 | #instVars : [ 8 | 'log' 9 | ], 10 | #category : 'TinyLogger-Tests-Core', 11 | #package : 'TinyLogger-Tests', 12 | #tag : 'Core' 13 | } 14 | 15 | { #category : 'helpers' } 16 | TinyFileLoggerTest >> actualClass [ 17 | ^ TinyFileLogger 18 | ] 19 | 20 | { #category : 'tests' } 21 | TinyFileLoggerTest >> fileNameNumber: number [ 22 | ^ 'testFile' , number asString , 'ForTinyLogger.log' 23 | ] 24 | 25 | { #category : 'running' } 26 | TinyFileLoggerTest >> tearDown [ 27 | log ifNotNil: #ensureDelete. 28 | super tearDown 29 | ] 30 | 31 | { #category : 'tests' } 32 | TinyFileLoggerTest >> testCanHaveDefaultFile [ 33 | self shouldnt: [ self actualClass new fileReference ] raise: Error 34 | ] 35 | 36 | { #category : 'tests' } 37 | TinyFileLoggerTest >> testClearLog [ 38 | log := (self fileNameNumber: 1) asFileReference. 39 | logger := self actualClass for: parentLogger named: log basename. 40 | logger record: 'this is a test'. 41 | 42 | self assert: log exists. 43 | logger clearLog. 44 | self deny: log exists 45 | ] 46 | 47 | { #category : 'tests' } 48 | TinyFileLoggerTest >> testFileName [ 49 | log := (self fileNameNumber: 4) asFileReference. 50 | logger := self actualClass for: parentLogger named: log basename. 51 | logger record: 'this is a test'. 52 | 53 | self assert: (self fileNameNumber: 4) asFileReference exists 54 | ] 55 | 56 | { #category : 'tests' } 57 | TinyFileLoggerTest >> testIndentationString [ 58 | 59 | log := (self fileNameNumber: 1) asFileReference. 60 | logger := self actualClass for: parentLogger named: log basename. 61 | parentLogger indentationString: '--'. 62 | 63 | logger execute: [ logger record: 'this is a test' ] recordedAs: 'block'. 64 | 65 | self assert: log exists. 66 | self assert: log contents lines isNotEmpty. 67 | self assert: (log contents lines second includesSubstring: '--this is a test') 68 | ] 69 | 70 | { #category : 'tests' } 71 | TinyFileLoggerTest >> testLoggerCreatesFile [ 72 | log := (self fileNameNumber: 10) asFileReference. 73 | logger := self actualClass for: parentLogger named: log basename. 74 | self deny: log exists. 75 | 76 | logger record: 'this is a test'. 77 | self assert: log exists 78 | ] 79 | 80 | { #category : 'tests' } 81 | TinyFileLoggerTest >> testRecord [ 82 | log := (self fileNameNumber: 1) asFileReference. 83 | logger := self actualClass for: parentLogger named: log basename. 84 | logger record: 'this is a test'. 85 | 86 | self assert: log exists. 87 | self assert: log contents lines isNotEmpty. 88 | self assert: (log contents lines last includesSubstring: 'this is a test') 89 | ] 90 | 91 | { #category : 'tests' } 92 | TinyFileLoggerTest >> testRecordSetFileToTheEnd [ 93 | log := (self fileNameNumber: 1) asFileReference. 94 | logger := self actualClass for: parentLogger named: log basename. 95 | logger record: 'this is a test'. 96 | 97 | self assert: log contents lines size equals: 1. 98 | logger record: 'this is a test2'. 99 | self assert: log contents lines size equals: 2 100 | ] 101 | -------------------------------------------------------------------------------- /src/TinyLogger/TinyLeafLogger.class.st: -------------------------------------------------------------------------------- 1 | " 2 | Description 3 | -------------------- 4 | 5 | I am an abstract class that should be subclasses by the concrete loggers of the project. Each of my subclass represent a different way to log an information (in a file, transcript, stdout...). 6 | 7 | Users should not use me or my subclasses directly. The user should manage logging via `TinyLogger` which is a composite of my subclasses. 8 | 9 | Public API and Key Messages 10 | -------------------- 11 | 12 | - #record: Record a string as parameter 13 | 14 | 15 | Internal Representation and Key Implementation Points. 16 | -------------------- 17 | 18 | Instance Variables 19 | parentLogger: As said before the user should use my subclasses via a `TinyLogger` composite. My parent logger is this `TinyLogger` instance. 20 | 21 | " 22 | Class { 23 | #name : 'TinyLeafLogger', 24 | #superclass : 'TinyAbstractLogger', 25 | #instVars : [ 26 | 'parentLogger' 27 | ], 28 | #category : 'TinyLogger-Core', 29 | #package : 'TinyLogger', 30 | #tag : 'Core' 31 | } 32 | 33 | { #category : 'instance creation' } 34 | TinyLeafLogger class >> for: aTinyLogger [ 35 | 36 | ^ self new 37 | parentLogger: aTinyLogger; 38 | in: [ :logger | aTinyLogger addLogger: logger ]; 39 | yourself 40 | ] 41 | 42 | { #category : 'testing' } 43 | TinyLeafLogger class >> isAbstract [ 44 | 45 | ^ self = TinyLeafLogger 46 | ] 47 | 48 | { #category : 'accessing' } 49 | TinyLeafLogger class >> kind [ 50 | 51 | ^ self subclassResponsibility 52 | ] 53 | 54 | { #category : 'accessing' } 55 | TinyLeafLogger >> depth [ 56 | ^ self parentLogger depth 57 | ] 58 | 59 | { #category : 'accessing' } 60 | TinyLeafLogger >> indentExecutionBlock [ 61 | 62 | ^ self parentLogger indentExecutionBlock 63 | ] 64 | 65 | { #category : 'accessing' } 66 | TinyLeafLogger >> indentationString [ 67 | 68 | ^ self parentLogger indentationString 69 | ] 70 | 71 | { #category : 'accessing' } 72 | TinyLeafLogger >> kind [ 73 | ^ self class kind 74 | ] 75 | 76 | { #category : 'logging' } 77 | TinyLeafLogger >> newLine [ 78 | ^ OSPlatform current lineEnding 79 | ] 80 | 81 | { #category : 'opening' } 82 | TinyLeafLogger >> open [ 83 | self subclassResponsibility 84 | ] 85 | 86 | { #category : 'accessing' } 87 | TinyLeafLogger >> parentLogger [ 88 | ^ parentLogger 89 | ] 90 | 91 | { #category : 'accessing' } 92 | TinyLeafLogger >> parentLogger: aLogger [ 93 | parentLogger := aLogger 94 | ] 95 | 96 | { #category : 'logging' } 97 | TinyLeafLogger >> record: aString on: aStream [ 98 | self recordPreambleOn: aStream. 99 | aStream << aString << self newLine 100 | ] 101 | 102 | { #category : 'accessing' } 103 | TinyLeafLogger >> recordPreambleOn: aStream [ 104 | 105 | 106 | self recordTimestampOn: aStream. 107 | aStream << ': '. 108 | self depth timesRepeat: [ aStream nextPutAll: self indentationString ] 109 | ] 110 | 111 | { #category : 'accessing' } 112 | TinyLeafLogger >> recordTimestampOn: aStream [ 113 | 114 | self timestampFormatBlock cull: aStream cull: DateAndTime now 115 | ] 116 | 117 | { #category : 'accessing' } 118 | TinyLeafLogger >> timestampFormatBlock [ 119 | ^ self parentLogger timestampFormatBlock 120 | ] 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinyLogger 2 | 3 | [![CI](https://github.com/jecisc/TinyLogger/actions/workflows/continuous.yml/badge.svg)](https://github.com/jecisc/TinyLogger/actions/workflows/continuous.yml) [![Coverage Status](https://coveralls.io/repos/github/jecisc/TinyLogger/badge.svg)](https://coveralls.io/github/jecisc/TinyLogger) 4 | 5 | A really small logger for Pharo applications. 6 | 7 | - [TinyLogger](#tinylogger) 8 | - [Installation](#installation) 9 | - [Quick start](#quick-start) 10 | - [Documentation](#documentation) 11 | - [Migration](#migration) 12 | - [Version management](#version-management) 13 | - [Smalltalk versions compatibility](#smalltalk-versions-compatibility) 14 | - [Contact](#contact) 15 | 16 | ## Installation 17 | 18 | To install TinyLogger on your Pharo image, execute the following script: 19 | 20 | ```Smalltalk 21 | Metacello new 22 | githubUser: 'jecisc' project: 'TinyLogger' commitish: 'v1.x.x' path: 'src'; 23 | baseline: 'TinyLogger'; 24 | load 25 | ``` 26 | 27 | To add TinyLogger to your baseline: 28 | 29 | ```Smalltalk 30 | spec 31 | baseline: 'TinyLogger' 32 | with: [ spec repository: 'github://jecisc/TinyLogger:v1.x.x/src' ] 33 | ``` 34 | 35 | Note you can replace the #master by another branch such as #development or a tag such as #v1.0.0, #v1.? or #v1.2.? . 36 | 37 | ## Quick start 38 | 39 | To create a file logger using a file `Progress.log` and records all messages as soon as the project is loaded in a a Pharo image, in the initialize method (class side) of a project put the following: 40 | 41 | ```Smalltalk 42 | TinyLogger default 43 | addFileLoggerNamed: 'Progress.log'. 44 | ``` 45 | 46 | To create a file logger that is reset each time you run an application. Put the following in the method that is run: 47 | 48 | ```Smalltalk 49 | TinyLogger default 50 | ensureFileLoggerNamed: 'Progress.log'; "Add the file logger only if not already" 51 | clearLog "This will delete the previous 'Progress.log' file". 52 | ``` 53 | 54 | Then write a message to the log using `record`: 55 | 56 | ```Smalltalk 57 | 'Uh oh. Something happened.' record 58 | ``` 59 | 60 | Or write a message to the log for the execution of an action using `execute:recordedAs:`: 61 | 62 | ```Smalltalk 63 | self execute: [ "Some code doing something" ] recordedAs: 'Launching bananas.' 64 | ``` 65 | 66 | Now, if you want to know more about the project, let's proceed on a more detailed documentation. 67 | 68 | ## Documentation 69 | 70 | Documentation is split into separate links as follows: 71 | * [User documentation](documentation/UserGuide.md) 72 | * [Developer documentation](documentation/DevelopmentGuide.md) 73 | 74 | ### Migration 75 | 76 | - [Migrate from v1 to v2](documentation/v1toV2.md) 77 | 78 | ## Version management 79 | 80 | This project use semantic versioning to define the releases. This means that each stable release of the project will be assigned a version number of the form `vX.Y.Z`. 81 | 82 | - **X** defines the major version number 83 | - **Y** defines the minor version number 84 | - **Z** defines the patch version number 85 | 86 | When a release contains only bug fixes, the patch number increases. When the release contains new features that are backward compatible, the minor version increases. When the release contains breaking changes, the major version increases. 87 | 88 | Thus, it should be safe to depend on a fixed major version and moving minor version of this project. 89 | 90 | ## Smalltalk versions compatibility 91 | 92 | | Version | Compatible Pharo versions | 93 | |------------- |------------------------------| 94 | | 1.x.x | Pharo 61, 70, 80, 90, 10, 11, 12, 13, 14 | 95 | | 2.x.x | Pharo 70, 80, 90, 10, 11, 12, 13, 14 | 96 | 97 | ## Contact 98 | 99 | If you have any questions or problems do not hesitate to open an issue or contact cyril (a) ferlicot.fr 100 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | > Note: Further changelogs will be available in the releases notes instead of this file since github now generates them automatically :) 7 | 8 | # [v1.3.2](https://github.com/jecisc/TinyLogger/compare/v1.3.1...v1.3.2) (2023-09-15) 9 | 10 | * Depend on more recent version of Mocktry ([8bbdf6b0](https://github.com/jecisc/TinyLogger/commit/8bbdf6b0a025dab104085b9ff5060c84d7239b58)) 11 | 12 | # [v1.3.1](https://github.com/jecisc/TinyLogger/compare/v1.3.0...v1.3.1) (2022-07-07) 13 | 14 | * Better error printing in #execute:recordedAs: 15 | 16 | # [v1.3.0](https://github.com/jecisc/TinyLogger/compare/v1.2.1...v1.3.0) (2022-06-02) 17 | 18 | * Add an option to open the log in a dedicatedl window (for terminal this is only on Windows currently) 19 | * #execute:recordedAs: now print a potential error that stopped the execution 20 | 21 | # [v1.2.1](https://github.com/jecisc/TinyLogger/compare/v1.2.0...v1.2.1) (2022-01-29) 22 | 23 | * Migrate CI to Github actions + support Pharo 9 ([c49255e](https://github.com/jecisc/TinyLogger/commit/c49255ebd6aa325ca335fe17fdd1041ff51b4f3a)) 24 | 25 | # [v1.2.0](https://github.com/jecisc/TinyLogger/compare/v1.1.5...v1.2.0) (2021-10-11) 26 | 27 | * #execute:recordedAs: should return the result of its block ([16eaa82](https://github.com/jecisc/TinyLogger/commit/16eaa82e22e803ca4226150dc047c5ff61bf5434)) 28 | 29 | # [v1.1.5](https://github.com/jecisc/TinyLogger/compare/v1.1.4...v1.1.5) (2020-03-16) 30 | 31 | * Manage errors inside action logging ([172a181](https://github.com/jecisc/TinyLogger/commit/172a1818df984b08b1ab6ebe60646c0aaf160771)) 32 | 33 | # [v1.1.4](https://github.com/jecisc/TinyLogger/compare/v1.1.3...v1.1.4) (2019-11-15) 34 | 35 | ## Bug fix 36 | 37 | * STDOut logger should flush after recoring ([6e6800a](https://github.com/jecisc/TinyLogger/commit/6e6800a5f167e3606d084290c7b3e4cfc2625985)) 38 | 39 | # [v1.1.3](https://github.com/jecisc/TinyLogger/compare/v1.1.2...v1.1.3) (2019-11-08) 40 | 41 | ## Infrastructure 42 | 43 | * Improve test coverage using MuTalk (mutation testing) ([716cd41](https://github.com/jecisc/TinyLogger/commit/716cd41655614326b4e20a426bdb58ce5f0f2031)) 44 | * Fix a title in the documentation ([1d6af61](https://github.com/jecisc/TinyLogger/commit/1d6af61b6114533ab20ef3ffaad252f95925d3a8)) 45 | 46 | # [v1.1.2](https://github.com/jecisc/TinyLogger/compare/v1.1.1...v1.1.2) (2019-09-25) 47 | 48 | ## Infrastructure 49 | 50 | * Update Mocketry dependency to v6.0.x ([c114fea](https://github.com/jecisc/TinyLogger/commit/c114feac2ff65bd11e11fcd7f188e09e90022465)) 51 | 52 | # [v1.1.1](https://github.com/jecisc/TinyLogger/compare/v1.1.0...v1.1.1) (2019-09-18) 53 | 54 | ## Bug fixes 55 | 56 | * Do not depend on whole Mocketry ([a50d354](https://github.com/jecisc/TinyLogger/commit/a50d3543bf00e76d780d7d3f2fb205280364b637)) 57 | 58 | ## Infrastructure 59 | 60 | * Deprecated method in Pharo 8 ([7f2ab1c](https://github.com/jecisc/TinyLogger/commit/7f2ab1c7a6fc43d23801faed40383e2b6d4279c3)) 61 | 62 | # [v1.1.0](https://github.com/jecisc/TinyLogger/compare/v1.0.1...v1.1.0) (2019-06-16) 63 | 64 | ## New Features 65 | 66 | * Add way to add a logger only if not already existing ([ed94153](https://github.com/jecisc/TinyLogger/commit/ed94153d9f086fed3a4f801cfe2029db383514c6)) 67 | * Add way to initialize remove old log like ensureDelete ([6529cc8](https://github.com/jecisc/TinyLogger/commit/6529cc8a99360d93ad80a3badff5066021b6053f)) 68 | 69 | ## Infrastructure 70 | 71 | * Add pharo 8 to ci ([2b7aded](https://github.com/jecisc/TinyLogger/commit/2b7adede42186b3aca18aea4c910421fc4b13558)) 72 | 73 | ## Documentation 74 | 75 | * Documentation v1.1.0 ([e928f63](https://github.com/jecisc/TinyLogger/commit/e928f635134e2081c6a85f24adb7b11bd4261fdf)) 76 | * Grammar review ([25b41de](https://github.com/jecisc/TinyLogger/commit/25b41de42009092ef15ba258ba70ea12b8b2384b)) 77 | * Grammar review 2 ([10e1a87](https://github.com/jecisc/TinyLogger/commit/10e1a87c45b54f07d9a5dd0205a29277447bbfc8)) 78 | * Grammar review 3 ([e279a10](https://github.com/jecisc/TinyLogger/commit/e279a10121503d0cddcb645c765773c14c68c457)) 79 | 80 | # [v1.0.1](https://github.com/jecisc/TinyLogger/compare/v1.0.0...v1.0.1) (2018-12-08) 81 | 82 | ## Bug fix 83 | 84 | * Fix stdout logging on Pharo 6.1 ([0d8ffdc](https://github.com/jecisc/TinyLogger/commit/0d8ffdc95724b2bf7a4859334469ddc63100f809)) 85 | -------------------------------------------------------------------------------- /documentation/DevelopmentGuide.md: -------------------------------------------------------------------------------- 1 | # Developer documentation 2 | 3 | This page will cover the developer documentation. It is recomanded to read the [user documentation](UserGuide.md) before this guide. 4 | 5 | A first part will cover the design of the project and a second part will be a tutorial to add a new leaf logger. 6 | 7 | - [Developer documentation](#developer-documentation) 8 | * [Design](#design) 9 | * [TinyCurrentLogger dynamic variable](#tinycurrentlogger-dynamic-variable) 10 | * [Add a new leaf logger](#add-a-new-leaf-logger) 11 | 12 | ## Design 13 | 14 | 15 | ![UML of the project](uml.png?raw=true "UML of the project") 16 | 17 | `TinyLogger` project is based on the [Composite design pattern](https://en.wikipedia.org/wiki/Composite_pattern). The logger to used, instance of `TinyLogger` is a composite containing `TinyLeafLogger`s. 18 | 19 | To add a new logger kind, `TinyLeafLogger` needs to be overriden. 20 | 21 | ## TinyCurrentLogger dynamic variable 22 | 23 | To record logs, the user will use extension methods implemented on `Object` as the main API. The `Object` will recover the `TinyLogger`'s instance to use to record the log via a DynamicVariable called `TinyCurrentLogger` like this: 24 | 25 | ```Smalltalk 26 | Object>>execute: aBlock recordedAs: aString 27 | TinyCurrentLogger value execute: aBlock recordedAs: aString 28 | ``` 29 | 30 | The `TinyCurrentLogger` has a default value that is the default instance of `TinyLogger`. This value can be changed using the `value:during:` message as explained in the user documentation. 31 | 32 | ## Add a new leaf logger 33 | 34 | In this section we will add a new leaf logger recording logs in a remote server via a HTTP request on a REST API. 35 | 36 | The first step is to create a subclass of `TinyLeafLogger`. If we want to make the URL to post on customizable, we need a variable `url`. 37 | 38 | ```Smalltalk 39 | TinyLeafLogger subclass: #TinyHTTPLogger 40 | slots: { #url } 41 | classVariables: { } 42 | package: 'CustomLogger' 43 | ``` 44 | 45 | Generates the accessors for the `url` variable and you can add a constructor: 46 | 47 | ```Smalltalk 48 | TinyHTTPLogger class>>url: aString 49 | ^ self new 50 | url: aString; 51 | yourself 52 | ``` 53 | 54 | Since `TinyLogger` groups its loggers by kinds, you need to define a `kind` method on the class side. 55 | 56 | ```Smalltalk 57 | TinyHTTPLogger class>>kind 58 | ^ 'HTTP' 59 | ``` 60 | 61 | Next step is to define the `record:` method to actually write the log. The superclass manages the preamble and formatting directly via the method `record:on:` that needs to be called in your `record:` method. 62 | 63 | ```Smalltalk 64 | TinyHTTPLogger>>record: aString 65 | ZnEasy post: self url data: (String streamContents: [ :s | self record: aString on: s ]) 66 | ``` 67 | 68 | At this point, our new logger is usable. You can add a new instance to your `TinyLogger` this way: 69 | 70 | ```Smalltalk 71 | TinyLogger default addLogger: (TinyHTTPLogger url: 'http://localhost:1234/api/logger') 72 | ``` 73 | 74 | But I recommand to add some suggar to the `TinyLogger` class with extension methods. 75 | 76 | The first one is to create and add a new HTTP logger: 77 | 78 | ```Smalltalk 79 | TinyLogger>>addHTTPLogger: aString 80 | self addLogger: (TinyHTTPLogger url: aString) 81 | ``` 82 | 83 | The logger can now be used like this: 84 | 85 | ```Smalltalk 86 | TinyLogger default addHTTPLogger: 'http://localhost:1234/api/logger' 87 | ``` 88 | 89 | Another method to add as extension is a method to get all the HTTP loggers: 90 | 91 | ```Smalltalk 92 | TinyLogger>>httpLoggers 93 | ^ self loggersMap at: TinyHTTPLogger kind ifAbsentPut: [ OrderedCollection new ] 94 | ``` 95 | 96 | Now that we have `httpLoggers`, we can easily add an `ensureHTTPLogger:` method matching `addHTTPLogger:`: 97 | 98 | ```Smalltalk 99 | TinyLogger>>ensureHTTPLogger: aString 100 | self httpLoggers 101 | detect: [ :e | e url = aString ] 102 | ifNone: [ self addHTTPLogger: aString ] 103 | ``` 104 | 105 | And the last method to add as extension will be used to remove all HTTP loggers of the logger: 106 | 107 | ```Smalltalk 108 | TinyLogger>>removeHTTPLoggers 109 | self httpLoggers removeAll 110 | ``` 111 | 112 | One last method to implement is `clearLogger` to clean the previous logs if the user want it: 113 | 114 | ```Smalltalk 115 | TinyHTTPLogger>>record: aString 116 | ZnClient new 117 | beOneShot; 118 | url: self url; 119 | delete 120 | ``` 121 | 122 | At this point you have one new logger kind available! 123 | -------------------------------------------------------------------------------- /documentation/uml.uxf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 4 | 5 | UMLClass 6 | 7 | 500 8 | 160 9 | 190 10 | 70 11 | 12 | /TinyAbstractLogger/ 13 | -- 14 | -- 15 | +record: aString 16 | +<< aString 17 | 18 | 19 | 20 | UMLClass 21 | 22 | 730 23 | 300 24 | 190 25 | 70 26 | 27 | /TinyLeafLogger/ 28 | -- 29 | -- 30 | 31 | 32 | 33 | UMLClass 34 | 35 | 320 36 | 300 37 | 250 38 | 160 39 | 40 | TinyLogger 41 | -- 42 | #depth : Integer 43 | #timestampFormatBlock: Block 44 | 45 | -- 46 | +addLogger: aLeafLogger 47 | +removeLogger: aLeafLogger 48 | +execute: aBlock recordedAs: aString 49 | 50 | 51 | 52 | UMLClass 53 | 54 | 590 55 | 450 56 | 150 57 | 60 58 | 59 | TinyTranscriptLogger 60 | -- 61 | -- 62 | 63 | 64 | 65 | UMLClass 66 | 67 | 760 68 | 450 69 | 130 70 | 60 71 | 72 | TinyStdoutLogger 73 | -- 74 | -- 75 | 76 | 77 | 78 | UMLClass 79 | 80 | 910 81 | 450 82 | 130 83 | 70 84 | 85 | TinyFileLogger 86 | -- 87 | #fileName : String 88 | -- 89 | 90 | 91 | 92 | Relation 93 | 94 | 650 95 | 360 96 | 190 97 | 110 98 | 99 | lt=<<- 100 | 170.0;10.0;170.0;40.0;10.0;40.0;10.0;90.0 101 | 102 | 103 | UMLClass 104 | 105 | 320 106 | 500 107 | 240 108 | 70 109 | 110 | TinyCurrentLogger 111 | -- 112 | -- 113 | +value: aTinyLogger during: aBlock 114 | 115 | 116 | 117 | Relation 118 | 119 | 810 120 | 360 121 | 30 122 | 110 123 | 124 | lt=<<- 125 | 10.0;10.0;10.0;90.0 126 | 127 | 128 | Relation 129 | 130 | 810 131 | 360 132 | 190 133 | 110 134 | 135 | lt=<<- 136 | 10.0;10.0;10.0;40.0;170.0;40.0;170.0;90.0 137 | 138 | 139 | Relation 140 | 141 | 580 142 | 220 143 | 270 144 | 100 145 | 146 | lt=<<- 147 | 10.0;10.0;10.0;50.0;250.0;50.0;250.0;80.0 148 | 149 | 150 | Relation 151 | 152 | 440 153 | 220 154 | 170 155 | 100 156 | 157 | lt=<<- 158 | 150.0;10.0;150.0;50.0;10.0;50.0;10.0;80.0 159 | 160 | 161 | Relation 162 | 163 | 560 164 | 330 165 | 190 166 | 40 167 | 168 | lt=<<<<- 169 | m1=1 170 | m2=0..* 171 | 10.0;10.0;170.0;10.0 172 | 173 | 174 | UMLNote 175 | 176 | 630 177 | 530 178 | 340 179 | 30 180 | 181 | Dynamic variable used by extensions on Object 182 | 183 | 184 | 185 | Relation 186 | 187 | 560 188 | 540 189 | 90 190 | 30 191 | 192 | lt=<- 193 | 10.0;10.0;70.0;10.0 194 | 195 | 196 | -------------------------------------------------------------------------------- /documentation/UserGuide.md: -------------------------------------------------------------------------------- 1 | # User documentation of TinyLogger 2 | 3 | `TinyLogger` is a logger implementation (no kidding?!) for Pharo whose goal is to be small and simple. 4 | 5 | Users can configure the way the logger works and the image will use this configured logger as default to record information. 6 | 7 | For specific cases, a specialized logger can be used. 8 | 9 | - [User documentation of TinyLogger](#user-documentation-of-tinylogger) 10 | - [Configure your logger](#configure-your-logger) 11 | - [Add sub-loggers to your `TinyLogger`](#add-sub-loggers-to-your-tinylogger) 12 | - [Remove sub-loggers](#remove-sub-loggers) 13 | - [List the sub-loggers](#list-the-sub-loggers) 14 | - [Record with your logger](#record-with-your-logger) 15 | - [Record a single line log](#record-a-single-line-log) 16 | - [Recording the execution of a task](#recording-the-execution-of-a-task) 17 | - [Lazy recording](#lazy-recording) 18 | - [Use a logger other than the global logger](#use-a-logger-other-than-the-global-logger) 19 | - [Clear your logger](#clear-your-logger) 20 | - [Further configurations](#further-configurations) 21 | - [Configure the timestamp](#configure-the-timestamp) 22 | - [Configure the identation string](#configure-the-identation-string) 23 | - [Add extra indentation block](#add-extra-indentation-block) 24 | - [Use special logger for tests](#use-special-logger-for-tests) 25 | 26 | 27 | ## Configure your logger 28 | 29 | The first step to use `TinyLogger` will be to configure the default logger that will be used by the image. 30 | 31 | For most of the cases, the user will only interact with `TinyLogger` class. `TinyLogger` is a composite to which *leaf* loggers can be added. *Leaf* loggers have the responsibility to log information in a specific manner. For example `TinyLogger` has a *leaf* logger to print information on Stdout of the application. 32 | 33 | ### Add sub-loggers to your `TinyLogger` 34 | 35 | The default instance of `TinyLogger` has no logger registered when it is initialized. The first step will be to add one or many loggers to it. 36 | 37 | Here is the list of loggers available by default: 38 | * `TinyStdoutLogger`: This logger records information on the *stdout* of the application. 39 | * `TinyTranscriptLogger`: This logger records information on the Pharo's `Transcript`. 40 | * `TinyFileLogger`: This logger records information in a file on the user file system. By default the file will be named `TinyLogger.log` and located in the image directory, but the file can be customized. 41 | 42 | A *leaf* logger can be added to the default logger via the method `addLogger:` like this: 43 | 44 | ```Smalltalk 45 | TinyLogger default addLogger: TinyStdoutLogger new 46 | ``` 47 | 48 | But all default *leaf* loggers have a method to create a new instance and the new instance to the `TinyLogger` already present. 49 | 50 | Here is an example adding a stdout logger, a transcript logger, a file logger with the default file name and a file logger with a customized name to the default `TinyLogger` instance: 51 | 52 | ```Smalltalk 53 | TinyLogger default 54 | addStdoutLogger; 55 | addTranscriptLogger; 56 | addFileLogger; 57 | addFileLoggerNamed: '../logs/MyOtherLog.log'. 58 | ``` 59 | 60 | Additionaly to those methods, `TinyLogger` also propose ways to add loggers only if they have no equivalent already registered. 61 | 62 | To archieve this result, use the equivalent of the methods previously explained, replacing `add` by `ensure`: 63 | ```Smalltalk 64 | TinyLogger default 65 | ensureStdoutLogger; 66 | ensureTranscriptLogger; 67 | ensureFileLogger; 68 | ensureFileLoggerNamed: '../logs/MyOtherLog.log'. 69 | ``` 70 | 71 | With those methods, Transcript and Stdout loggers will be limited to one, and file loggers will be limited to one by file name. 72 | 73 | ### Remove sub-loggers 74 | 75 | If at some point you want to remove one or multiple loggers, `TinyLogger` has an API for that. 76 | 77 | The first way to remove loggers is with the method `removeAllLoggers`, which removes all the loggers of each kind. 78 | 79 | ```Smalltalk 80 | TinyLogger default removeAllLoggers 81 | ``` 82 | 83 | A second way to remove loggers is to remove them by kind: 84 | * `removeFileLoggers` 85 | * `removeTranscriptLoggers` 86 | * `removeStdoutLoggers` 87 | 88 | ```Smalltalk 89 | TinyLogger default removeFileLoggers 90 | ``` 91 | 92 | A last way is to use the method `removeLogger:` with the logger to remove. 93 | 94 | ```Smalltalk 95 | TinyLogger default fileLoggers 96 | select: [ :logger | logger fileName beginsWith: 'Test' ] 97 | thenDo: [ :logger | TinyLogger default removeLogger: logger ] 98 | ``` 99 | 100 | ### List the sub-loggers 101 | 102 | If you wish to get the list of loggers in a `TinyLogger` you can use the `loggers` method: 103 | 104 | ```Smalltalk 105 | TinyLogger default loggers 106 | ``` 107 | 108 | You can also get it in the form of a dictionary with the kind of logger as keys and the instanciated loggers of this kind as values via the `loggersMap` method. 109 | 110 | ```Smalltalk 111 | TinyLogger default loggersMap 112 | ``` 113 | 114 | If you with to know all the loggers of a kind, you can use: 115 | * `fileLoggers` 116 | * `transcriptLoggers` 117 | * `stdoutLoggers` 118 | 119 | ## Record with your logger 120 | 121 | This section will cover the API to record information. 122 | 123 | ### Record a single line log 124 | 125 | To record a single line on a log, you can just use the method `record`: 126 | 127 | ```Smalltalk 128 | 'This is a string to log' record 129 | ``` 130 | 131 | This will produce a log like this with the default `timestampFormatBlock`: 132 | 133 | ``` 134 | 2018-11-29T23:19:55.511775+01:00 : This is a string to log 135 | ``` 136 | 137 | ### Recording the execution of a task 138 | 139 | To record the execution of a task you can use the method `execute:recordedAs:` 140 | 141 | ```Smalltalk 142 | self execute: [ 1 to: 5 do: [ :value | value asString record ] ] recordedAs: 'Task with only one nesting.' 143 | ``` 144 | 145 | Will produce a log like this: 146 | 147 | ``` 148 | 2018-11-29T23:21:04.897775+01:00: Begin: Task with only one nesting. 149 | 2018-11-29T23:21:04.900775+01:00: 1 150 | 2018-11-29T23:21:04.902775+01:00: 2 151 | 2018-11-29T23:21:04.904775+01:00: 3 152 | 2018-11-29T23:21:04.906775+01:00: 4 153 | 2018-11-29T23:21:04.908775+01:00: 5 154 | 2018-11-29T23:21:04.909775+01:00: End: Task with only one nesting. 155 | ``` 156 | 157 | It is also possible to nest them: 158 | 159 | ```Smalltalk 160 | self execute: [ 161 | 1 to: 4 do: [ :value1 | 162 | self execute: [ 163 | 1 to: value1 do: [ :value2 | value2 asString record ] 164 | ] recordedAs: 'My second nest' 165 | ] 166 | ] recordedAs: 'My first nest'. 167 | ``` 168 | 169 | It will produce this kind of output: 170 | 171 | ``` 172 | 2018-11-29T23:21:20.147775+01:00: Begin: My first nest 173 | 2018-11-29T23:21:20.151775+01:00: Begin: My second nest 174 | 2018-11-29T23:21:20.153775+01:00: 1 175 | 2018-11-29T23:21:20.155775+01:00: End: My second nest 176 | 2018-11-29T23:21:20.157775+01:00: Begin: My second nest 177 | 2018-11-29T23:21:20.158775+01:00: 1 178 | 2018-11-29T23:21:20.160775+01:00: 2 179 | 2018-11-29T23:21:20.161775+01:00: End: My second nest 180 | 2018-11-29T23:21:20.163775+01:00: Begin: My second nest 181 | 2018-11-29T23:21:20.164775+01:00: 1 182 | 2018-11-29T23:21:20.165775+01:00: 2 183 | 2018-11-29T23:21:20.167775+01:00: 3 184 | 2018-11-29T23:21:20.169775+01:00: End: My second nest 185 | 2018-11-29T23:21:20.171775+01:00: Begin: My second nest 186 | 2018-11-29T23:21:20.172775+01:00: 1 187 | 2018-11-29T23:21:20.175775+01:00: 2 188 | 2018-11-29T23:21:20.176775+01:00: 3 189 | 2018-11-29T23:21:20.177775+01:00: 4 190 | 2018-11-29T23:21:20.179775+01:00: End: My second nest 191 | 2018-11-29T23:21:20.180775+01:00: End: My first nest 192 | ``` 193 | 194 | ## Lazy recording 195 | 196 | In some cases we might want to record things and it takes time to build the string to record. In that case, it is possible to use a block to do the recording, and this block will be executed if there is at least one logger registered. Like this, if no logger is set, the block will be ignored and the application will not slow down. 197 | 198 | ```Smalltalk 199 | 200 | self record: [ String streamContents: [ :aStream | aStream nextPutAll: '{ something to compute }' ] ]. 201 | 202 | [ String streamContents: [ :aStream | aStream nextPutAll: '{ something to compute }' ] ] record 203 | ``` 204 | 205 | > #execute:recordedAs: works in the same way. It is possible to pass a block as a second parameter and if no logger is present, this block will not be executed. 206 | 207 | ## Use a logger other than the global logger 208 | 209 | In some cases we may not want to use the default logger for a part of the application. 210 | 211 | `TinyLogger` allows using a custom logger during the execution of a process by changing the value of `TinyCurrentLogger`: 212 | 213 | ```Smalltalk 214 | | customLogger | 215 | customLogger := TinyLogger new 216 | addTranscriptLogger; 217 | yourself. 218 | 219 | TinyCurrentLogger value: customLogger during: [ 220 | 'test' record. 221 | TinyCurrentLogger value record: 'Test2' 222 | ] 223 | ``` 224 | 225 | ## Clear your logger 226 | 227 | Each logger understands the method `#clearLog`. This method will have as effect to clear the output of the loggers. The actual effect is different depending on the kind of logger: 228 | - `TinyLogger` will send the message to all its sub loggers 229 | - `Transcript` logger will clear the Transcript of Pharo 230 | - `Stdout` logger will do nothing because it is not possible to clean a stdout 231 | - `File` logger will erase the file used to log 232 | 233 | ```Smalltalk 234 | TinyLogger default clearLogger 235 | ``` 236 | 237 | ## Further configurations 238 | 239 | ### Configure the timestamp 240 | 241 | By default, the preamble of the log will be a timestamp with a human readable format, e.g.: 242 | 243 | ``` 244 | 2018-11-29T23:19:55.511775+01:00: Test 245 | ``` 246 | 247 | But this format is configurable. The `timestampFormatBlock:` method can be used with a parameter that is a block taking a stream as parameter and the timestamp (instance of `DateAndTime`) and use that to write the preamble on the stream. 248 | 249 | ```Smalltalk 250 | TinyLogger default 251 | timestampFormatBlock: [ :aStream :timestamp | 252 | timestamp asDate printOn: aStream. 253 | aStream << ' '. "Cannot use #space because of Stdio streams" 254 | timestamp asTime print24: true on: aStream ] 255 | ``` 256 | 257 | This will produce logs of this format: 258 | 259 | ``` 260 | 29 November 2018 00:06:30: Test 261 | ``` 262 | 263 | ### Configure the identation string 264 | 265 | By default using #`execute:recordedAs:` will use a tab for identation. It is possible to configure this using `#identationString:` to have, for example, spaces. 266 | 267 | ```Smalltalk 268 | TinyLogger default identationString: ' '. "Two spaces" 269 | self execute: [ 'Log' record ] recordedAs: 'Task' 270 | ``` 271 | 272 | Will produce a log like this: 273 | 274 | ``` 275 | 2018-11-29T23:21:04.897775+01:00: Begin: Task 276 | 2018-11-29T23:21:04.900775+01:00: Log 277 | 2018-11-29T23:21:04.909775+01:00: End: Task 278 | ``` 279 | 280 | On the second line, the identation will use two spaces instead of a tab that is the default value. 281 | 282 | ### Add extra indentation block 283 | 284 | Depending on the preferences of the developper it is possible to use #`indentExecutionBlock` to add an extra indentation to the block of `#execute:recordedAs:`. 285 | 286 | ```Smalltalk 287 | TinyLogger default indentExecutionBlock: true. 288 | self execute: [ 'Log' record ] recordedAs: 'Task' 289 | ``` 290 | 291 | Will produce a log like this: 292 | 293 | ``` 294 | 2018-11-29T23:21:04.897775+01:00: Begin: Task 295 | 2018-11-29T23:21:04.900775+01:00: Log 296 | 2018-11-29T23:21:04.909775+01:00: End: Task 297 | ``` 298 | 299 | Instead of 300 | 301 | ``` 302 | 2018-11-29T23:21:04.897775+01:00: Begin: Task 303 | 2018-11-29T23:21:04.900775+01:00: Log 304 | 2018-11-29T23:21:04.909775+01:00: End: Task 305 | ``` 306 | 307 | ## Use special logger for tests 308 | 309 | It is probable that you might not want to have your default logger for tests. It is possible to archive this by overriding the #performTest method of your TestCase like this: 310 | 311 | ```Smalltalk 312 | MyTestCase>>performTest 313 | | testLogger | 314 | testLogger := TinyLogger new 315 | addTranscriptLogger; 316 | yourself. 317 | 318 | TinyCurrentLogger value: testLogger during: [ 319 | super performTest 320 | ] 321 | ``` 322 | 323 | Doing this, your tests will use the `testLogger` instead of the default logger in the image. 324 | -------------------------------------------------------------------------------- /src/TinyLogger/TinyLogger.class.st: -------------------------------------------------------------------------------- 1 | " 2 | Description 3 | -------------------- 4 | 5 | I am the main entry point of the project. This project is build with a composite design pattern and I am the composite. I contains subclasses of TinyLeafLogger. 6 | 7 | I am a default instance that is used by default in the image when you send #record to a string for example. This instance should be configured by the user before been used (else it will do nothing). 8 | It is also possible to create another instance with different loggers and to use it. You can also change the default logger objects will use to record logs with the `TinyCurrentLogger` dynamic variable (See example bellow). 9 | 10 | The user can configure the timestamp formating via the #timestampFormatBlock paramater. 11 | 12 | Public API and Key Messages 13 | -------------------- 14 | 15 | - #record:  Log the parameter of the sub loggers 16 | - #clearLog  Clear the output of the sub loggers 17 | - #execute:recordedAs: Execute a block and log the task when it beging and ends. This will increase the identation of the logging 18 | - #addStdoutLogger Add a logger on Stdout 19 | - #addTrascriptLogger Add a logger on the Transcript 20 | - #addFileLogger Add a logger that will log in a file named `TinyLogger.log` 21 | - #addFileLoggerNamed: Add a logger on a file whose name is passed as parameter 22 | - #ensure: Variant of all previous #addX message. This one will add the new logger only if there is not equivalent logger already present 23 | - #timestampFormatBlock: Give as parameter a block taking a stream as parameter and writing the timestamp you want 24 | - #indentationString: Allow to customize the identation string. By default it is a tab, but you can use spaces instead. 25 | - #indentExecutionBlock: Allow to add an extra indentation to block from #execute:recordedAs: . 26 | 27 | Examples 28 | -------------------- 29 | 30 | ```Smalltalk 31 | ""Add two file loggers, a stdio logger and a transcript logger to the default logger."" 32 | TinyLogger default 33 | addTranscriptLogger; 34 | addStdoutLogger; 35 | addFileLoggerNamed: 'Test.log'; 36 | addFileLoggerNamed: 'MyOtherLog.log'. 37 | 38 | ""Record a string via the default logger"" 39 | 'My string' record. 40 | TinyLogger default record: 'My string'. 41 | 42 | ""Record with nesting via the default logger"" 43 | self execute: [ 1 to: 10 do: [ :value | value asString record ] ] recordedAs: 'Task with only one nesting.' 44 | 45 | self execute: [ 46 | 1 to: 10 do: [ :value1 | 47 | self execute: [ 48 | 1 to: value1 do: [ :value2 | value2 asString record ] 49 | ] recordedAs: 'My second nest' 50 | ] 51 | ] recordedAs: 'My first nest'. 52 | 53 | ""Record with a non default logger"" 54 | TinyLogger new 55 | addTranscriptLogger; 56 | record: 'Test'. 57 | 58 | ""Change the default logger Objects should use during the execution of a subpart of the system"" 59 | TinyCurrentLogger value: (TinyLogger new addTranscriptLogger; yourself) during: [ 60 | 'test' record. 61 | TinyCurrentLogger value record: 'Test2' 62 | ] 63 | 64 | ""Change the default timestamp format or identation string"" 65 | TinyLogger default 66 | timestampFormatBlock: [ :aStream :timestamp | timestamp asDate printOn: aStream. aStream << ' '. ""Cannot use #space because of Stdio streams"" timestamp asTime print24: true on: aStream ]; 67 | identationString: ' '; 68 | indentExecutionBlock: true. 69 | ``` 70 | 71 | Internal Representation and Key Implementation Points. 72 | -------------------- 73 | 74 | Instance Variables 75 | depth: I record the depth level of the nesting. 76 | loggersMap: I am used to store the loggers. I keep for each kind of loggers a collection of their instances. 77 | timestampFormatBlock: I am a block defining the way the timestamp should be written. 78 | identationString: I am a String used to ident logs. 79 | indentExecutionBlock: Allow to add an extra indentation to block from #execute:recordedAs: . 80 | 81 | " 82 | Class { 83 | #name : 'TinyLogger', 84 | #superclass : 'TinyAbstractLogger', 85 | #instVars : [ 86 | 'timestampFormatBlock', 87 | 'indentationString', 88 | 'depth', 89 | 'loggersMap', 90 | 'indentExecutionBlock' 91 | ], 92 | #classVars : [ 93 | 'Default' 94 | ], 95 | #category : 'TinyLogger-Core', 96 | #package : 'TinyLogger', 97 | #tag : 'Core' 98 | } 99 | 100 | { #category : 'accessing' } 101 | TinyLogger class >> default [ 102 | ^ Default ifNil: [ Default := self new ] 103 | ] 104 | 105 | { #category : 'accessing' } 106 | TinyLogger class >> default: anObject [ 107 | Default := anObject 108 | ] 109 | 110 | { #category : 'initialization' } 111 | TinyLogger class >> reset [ 112 | self default: nil 113 | ] 114 | 115 | { #category : 'public API' } 116 | TinyLogger >> addFileLogger [ 117 | "Since no file name is passed as parameter, I will log in a file named `TinyLogger.log` next to the image." 118 | 119 | self addLogger: TinyFileLogger new 120 | ] 121 | 122 | { #category : 'public API' } 123 | TinyLogger >> addFileLoggerNamed: aString [ 124 | ^ self addLogger: (TinyFileLogger named: aString) 125 | ] 126 | 127 | { #category : 'public API' } 128 | TinyLogger >> addLogger: aLogger [ 129 | 130 | ^ (self loggersMap at: aLogger kind ifAbsentPut: [ OrderedCollection new ]) add: (aLogger parentLogger: self) 131 | ] 132 | 133 | { #category : 'public API' } 134 | TinyLogger >> addStdoutLogger [ 135 | self addLogger: TinyStdoutLogger new 136 | ] 137 | 138 | { #category : 'public API' } 139 | TinyLogger >> addTranscriptLogger [ 140 | self addLogger: TinyTranscriptLogger new 141 | ] 142 | 143 | { #category : 'logging' } 144 | TinyLogger >> clearLog [ 145 | self loggers do: #clearLog 146 | ] 147 | 148 | { #category : 'nesting' } 149 | TinyLogger >> decreaseDepthLevel [ 150 | self depth: self depth - 1 151 | ] 152 | 153 | { #category : 'accessing' } 154 | TinyLogger >> defaultTimestampFormatBlock [ 155 | ^ [ :aStream :timestamp | aStream << timestamp asString ] 156 | ] 157 | 158 | { #category : 'accessing' } 159 | TinyLogger >> depth [ 160 | ^ depth 161 | ] 162 | 163 | { #category : 'accessing' } 164 | TinyLogger >> depth: anObject [ 165 | depth := anObject 166 | ] 167 | 168 | { #category : 'public API' } 169 | TinyLogger >> ensureFileLogger [ 170 | "Ensure a file logger to `TinyLogger.log` is in the logger. In case one already exists, does nothing." 171 | 172 | self ensureFileLoggerNamed: TinyFileLogger defaultFileName 173 | ] 174 | 175 | { #category : 'public API' } 176 | TinyLogger >> ensureFileLoggerNamed: aString [ 177 | "Ensure a file logger to a file whose name is given as parameter is in the logger. In case one already exists, does nothing." 178 | 179 | ^ self fileLoggers 180 | detect: [ :e | e fileName = aString ] 181 | ifNone: [ self addFileLoggerNamed: aString ] 182 | ] 183 | 184 | { #category : 'public API' } 185 | TinyLogger >> ensureStdoutLogger [ 186 | self stdoutLoggers ifEmpty: [ self addStdoutLogger ] 187 | ] 188 | 189 | { #category : 'public API' } 190 | TinyLogger >> ensureTranscriptLogger [ 191 | self transcriptLoggers ifEmpty: [ self addTranscriptLogger ] 192 | ] 193 | 194 | { #category : 'public API' } 195 | TinyLogger >> execute: aBlock recordedAs: aBlockOrString [ 196 | 197 | | logMessage result errorMessage | 198 | logMessage := aBlockOrString isBlock 199 | ifTrue: [ aBlockOrString value ] 200 | ifFalse: [ aBlockOrString ]. 201 | self hasLoggers ifFalse: [ ^ aBlock cull: logMessage ]. 202 | self indentExecutionBlock ifTrue: [ self increaseDepthLevel ]. 203 | self record: 'Begin: ' , logMessage. 204 | self increaseDepthLevel. 205 | [ 206 | [ result := aBlock cull: logMessage ] 207 | on: Error 208 | do: [ :exception | "If there is an error, we keep the message and pass it. The message will be used in the #ifCurtailed: to ensure we keep the right indentation and print a better result to the user." 209 | errorMessage := exception printString. 210 | exception pass ] ] ifCurtailed: [ "If there is an unexpected termination of the execution, print that there was an error." 211 | self decreaseDepthLevel. 212 | self record: 'End with error: ' , logMessage , '.' , (errorMessage 213 | ifNil: [ '' ] 214 | ifNotNil: [ ' Error message: "' , errorMessage , '"' ]). 215 | self indentExecutionBlock ifTrue: [ self decreaseDepthLevel ] ]. 216 | 217 | self decreaseDepthLevel. 218 | self record: 'End: ' , logMessage. 219 | self indentExecutionBlock ifTrue: [ self decreaseDepthLevel ]. 220 | ^ result 221 | ] 222 | 223 | { #category : 'accessing - loggers' } 224 | TinyLogger >> fileLoggers [ 225 | ^ self loggersMap at: TinyFileLogger kind ifAbsentPut: [ OrderedCollection new ] 226 | ] 227 | 228 | { #category : 'testing' } 229 | TinyLogger >> hasLoggers [ 230 | ^ self loggersMap isNotEmpty 231 | ] 232 | 233 | { #category : 'nesting' } 234 | TinyLogger >> increaseDepthLevel [ 235 | self depth: self depth + 1 236 | ] 237 | 238 | { #category : 'accessing' } 239 | TinyLogger >> indentExecutionBlock [ 240 | "By setting this to true, it will add an extra indentation to #execute:recordedAs: blocks. 241 | 242 | Setting to true: 243 | 244 | 2018-11-29T23:21:04.897775+01:00: Begin: Task 245 | 2018-11-29T23:21:04.900775+01:00: Log 246 | 2018-11-29T23:21:04.909775+01:00: End: Task 247 | 248 | Setting to false: 249 | 250 | 2018-11-29T23:21:04.897775+01:00: Begin: Task 251 | 2018-11-29T23:21:04.900775+01:00: Log 252 | 2018-11-29T23:21:04.909775+01:00: End: Task 253 | " 254 | ^ indentExecutionBlock 255 | ] 256 | 257 | { #category : 'accessing' } 258 | TinyLogger >> indentExecutionBlock: anObject [ 259 | 260 | indentExecutionBlock := anObject 261 | ] 262 | 263 | { #category : 'accessing' } 264 | TinyLogger >> indentationString [ 265 | 266 | ^ indentationString 267 | ] 268 | 269 | { #category : 'accessing' } 270 | TinyLogger >> indentationString: aString [ 271 | 272 | indentationString := aString 273 | ] 274 | 275 | { #category : 'initialization' } 276 | TinyLogger >> initialize [ 277 | 278 | super initialize. 279 | self depth: 0. 280 | self timestampFormatBlock: self defaultTimestampFormatBlock. 281 | self loggersMap: Dictionary new. 282 | self indentationString: String tab. 283 | self indentExecutionBlock: false 284 | ] 285 | 286 | { #category : 'accessing' } 287 | TinyLogger >> loggers [ 288 | "Since the loggers variable contains a dictionary of loggers, here we faltten the dictonary." 289 | 290 | ^ self loggersMap values flatCollect: #yourself 291 | ] 292 | 293 | { #category : 'accessing' } 294 | TinyLogger >> loggersMap [ 295 | ^ loggersMap 296 | ] 297 | 298 | { #category : 'accessing' } 299 | TinyLogger >> loggersMap: anObject [ 300 | loggersMap := anObject 301 | ] 302 | 303 | { #category : 'public API' } 304 | TinyLogger >> record: aBlockOrString [ 305 | "If a block is provided, it is only evaluated if a logger is present. 306 | The block must return a string that will be recorded." 307 | 308 | | aString | 309 | self hasLoggers ifFalse: [ ^ self ]. 310 | aString := aBlockOrString isBlock 311 | ifTrue: [ aBlockOrString value ] 312 | ifFalse: [ aBlockOrString ]. 313 | 314 | self loggers do: [ :each | each record: aString ] 315 | ] 316 | 317 | { #category : 'public API' } 318 | TinyLogger >> removeAllLoggers [ 319 | self loggers do: [ :each | self removeLogger: each ] 320 | ] 321 | 322 | { #category : 'public API' } 323 | TinyLogger >> removeFileLoggers [ 324 | self fileLoggers removeAll 325 | ] 326 | 327 | { #category : 'public API' } 328 | TinyLogger >> removeLogger: aLogger [ 329 | (self loggersMap at: aLogger kind ifAbsentPut: [ OrderedCollection new ]) remove: aLogger 330 | ] 331 | 332 | { #category : 'public API' } 333 | TinyLogger >> removeStdoutLoggers [ 334 | self stdoutLoggers removeAll 335 | ] 336 | 337 | { #category : 'public API' } 338 | TinyLogger >> removeTranscriptLoggers [ 339 | self transcriptLoggers removeAll 340 | ] 341 | 342 | { #category : 'accessing - loggers' } 343 | TinyLogger >> stdoutLoggers [ 344 | ^ self loggersMap at: TinyStdoutLogger kind ifAbsentPut: [ OrderedCollection new ] 345 | ] 346 | 347 | { #category : 'accessing' } 348 | TinyLogger >> timestampFormatBlock [ 349 | ^ timestampFormatBlock 350 | ] 351 | 352 | { #category : 'accessing' } 353 | TinyLogger >> timestampFormatBlock: anObject [ 354 | timestampFormatBlock := anObject 355 | ] 356 | 357 | { #category : 'accessing - loggers' } 358 | TinyLogger >> transcriptLoggers [ 359 | ^ self loggersMap at: TinyTranscriptLogger kind ifAbsentPut: [ OrderedCollection new ] 360 | ] 361 | 362 | { #category : 'public API' } 363 | TinyLogger >> withDefaultLoggers [ 364 | self 365 | removeAllLoggers; 366 | addTranscriptLogger; 367 | addStdoutLogger; 368 | addFileLogger 369 | ] 370 | -------------------------------------------------------------------------------- /src/TinyLogger-Tests/TinyLoggerTest.class.st: -------------------------------------------------------------------------------- 1 | " 2 | A TinyLoggerTest is a test class for testing the behavior of TinyLogger 3 | " 4 | Class { 5 | #name : 'TinyLoggerTest', 6 | #superclass : 'TinyAbstractLoggerTest', 7 | #category : 'TinyLogger-Tests-Core', 8 | #package : 'TinyLogger-Tests', 9 | #tag : 'Core' 10 | } 11 | 12 | { #category : 'helpers' } 13 | TinyLoggerTest >> actualClass [ 14 | ^ TinyLogger 15 | ] 16 | 17 | { #category : 'private' } 18 | TinyLoggerTest >> performTest [ 19 | 20 | TinyCurrentLogger value: logger during: [ super performTest ] 21 | ] 22 | 23 | { #category : 'running' } 24 | TinyLoggerTest >> setUp [ 25 | super setUp. 26 | logger := TinyLogger new 27 | ] 28 | 29 | { #category : 'tests' } 30 | TinyLoggerTest >> testAddFileLogger [ 31 | logger removeAllLoggers. 32 | self assert: logger loggers isEmpty. 33 | 34 | logger 35 | addFileLogger; 36 | addFileLogger. 37 | self assert: logger loggers size equals: 2. 38 | self assert: logger fileLoggers size equals: 2 39 | ] 40 | 41 | { #category : 'tests' } 42 | TinyLoggerTest >> testAddFileLoggerNamed [ 43 | logger removeAllLoggers. 44 | self assert: logger loggers isEmpty. 45 | 46 | logger addFileLoggerNamed: 'test.log'. 47 | self assert: logger fileLoggers size equals: 1. 48 | self assert: logger loggers anyOne fileName equals: 'test.log' 49 | ] 50 | 51 | { #category : 'tests' } 52 | TinyLoggerTest >> testAddLogger [ 53 | 54 | logger removeAllLoggers. 55 | self flag: #Pharo7. "When P7 compatibility will be dropped, use #assertEmpty: here and in other tests." 56 | self assert: logger loggers isEmpty. 57 | 58 | logger 59 | addLogger: TinyFileLogger new; 60 | addLogger: (TinyFileLogger named: 'test.log'). 61 | self assert: logger loggers size equals: 2. 62 | self assert: logger fileLoggers size equals: 2 63 | ] 64 | 65 | { #category : 'tests' } 66 | TinyLoggerTest >> testAddStdoutLogger [ 67 | logger removeAllLoggers. 68 | self assert: logger loggers isEmpty. 69 | 70 | logger 71 | addStdoutLogger; 72 | addStdoutLogger. 73 | self assert: logger loggers size equals: 2. 74 | self assert: logger stdoutLoggers size equals: 2 75 | ] 76 | 77 | { #category : 'tests' } 78 | TinyLoggerTest >> testAddTranscriptLogger [ 79 | logger removeAllLoggers. 80 | self assert: logger loggers isEmpty. 81 | 82 | logger 83 | addTranscriptLogger; 84 | addTranscriptLogger. 85 | self assert: logger loggers size equals: 2. 86 | self assert: logger transcriptLoggers size equals: 2 87 | ] 88 | 89 | { #category : 'tests' } 90 | TinyLoggerTest >> testClearLog [ 91 | logger 92 | addTranscriptLogger; 93 | addFileLoggerNamed: 'testFileForTinyLogger.log'; 94 | addFileLoggerNamed: 'testFileForTinyLogger2.log'. 95 | 96 | logger record: 'Test'. 97 | self assert: (logger fileLoggers allSatisfy: [ :fileLogger | fileLogger fileReference exists ]). 98 | 99 | logger clearLog. 100 | self assert: (logger fileLoggers noneSatisfy: [ :fileLogger | fileLogger fileReference exists ]) 101 | ] 102 | 103 | { #category : 'tests' } 104 | TinyLoggerTest >> testCurrentLogger [ 105 | self assert: TinyCurrentLogger value isNotNil. 106 | self assert: TinyCurrentLogger value class equals: TinyLogger 107 | ] 108 | 109 | { #category : 'tests' } 110 | TinyLoggerTest >> testEnsureFileLogger [ 111 | logger removeAllLoggers. 112 | self assert: logger loggers isEmpty. 113 | 114 | logger ensureFileLogger. 115 | self assert: logger loggers size equals: 1. 116 | self assert: logger fileLoggers size equals: 1. 117 | 118 | logger ensureFileLogger. 119 | self assert: logger loggers size equals: 1. 120 | self assert: logger fileLoggers size equals: 1 121 | ] 122 | 123 | { #category : 'tests' } 124 | TinyLoggerTest >> testEnsureFileLoggerNamed [ 125 | logger removeAllLoggers. 126 | self assert: logger loggers isEmpty. 127 | 128 | logger ensureFileLoggerNamed: 'test.log'. 129 | self assert: logger fileLoggers size equals: 1. 130 | 131 | logger ensureFileLoggerNamed: 'test.log'. 132 | logger ensureFileLoggerNamed: 'test2.log'. 133 | self assert: logger fileLoggers size equals: 2 134 | ] 135 | 136 | { #category : 'tests' } 137 | TinyLoggerTest >> testEnsureStdoutLogger [ 138 | logger removeAllLoggers. 139 | self assert: logger loggers isEmpty. 140 | 141 | logger 142 | ensureStdoutLogger; 143 | ensureStdoutLogger. 144 | self assert: logger loggers size equals: 1. 145 | self assert: logger stdoutLoggers size equals: 1 146 | ] 147 | 148 | { #category : 'tests' } 149 | TinyLoggerTest >> testEnsureTranscriptLogger [ 150 | logger removeAllLoggers. 151 | self assert: logger loggers isEmpty. 152 | 153 | logger 154 | ensureTranscriptLogger; 155 | ensureTranscriptLogger. 156 | self assert: logger loggers size equals: 1. 157 | self assert: logger transcriptLoggers size equals: 1 158 | ] 159 | 160 | { #category : 'tests' } 161 | TinyLoggerTest >> testExecuteRecordedAs [ 162 | | contents stream bool | 163 | bool := false. 164 | logger 165 | removeAllLoggers; 166 | addStdoutLogger. 167 | stream := '' writeStream. 168 | [ Stdio stub stdout willReturn: stream. 169 | logger execute: [ bool := true ] recordedAs: 'This is a new test'. 170 | contents := Stdio stdout contents asString. 171 | self assert: bool. 172 | self assert: (contents includesSubstring: 'Begin: This is a new test'). 173 | self assert: (contents includesSubstring: 'End: This is a new test') ] 174 | ensure: [ Stdio recoverFromGHMutation. 175 | stream close ] 176 | ] 177 | 178 | { #category : 'tests' } 179 | TinyLoggerTest >> testExecuteRecordedAs2 [ 180 | | contents stream | 181 | logger 182 | timestampFormatBlock: [ :s | s nextPutAll: 'No time' ]; 183 | removeAllLoggers; 184 | addStdoutLogger. 185 | stream := '' writeStream. 186 | [ Stdio stub stdout willReturn: stream. 187 | Object new execute: [ 'test' record ] recordedAs: 'This is a new test'. 188 | contents := Stdio stdout contents asString. 189 | 190 | "Ensure we have the right indentation." 191 | self 192 | assert: contents withUnixLineEndings 193 | equals: 194 | 'No time: Begin: This is a new test 195 | No time: test 196 | No time: End: This is a new test 197 | ' withUnixLineEndings ] 198 | ensure: [ Stdio recoverFromGHMutation. 199 | stream close ] 200 | ] 201 | 202 | { #category : 'tests' } 203 | TinyLoggerTest >> testExecuteRecordedAsBlock [ 204 | | log | 205 | log := 'testFileForTinyLogger.log' asFileReference. 206 | [ 207 | logger execute: [ ] recordedAs: [ 'this is a test' ]. 208 | 209 | self deny: log exists ] 210 | ensure: [ (log isNotNil and: [ log exists ]) 211 | ifTrue: [ log ensureDelete ] ] 212 | ] 213 | 214 | { #category : 'tests' } 215 | TinyLoggerTest >> testExecuteRecordedAsBlock2 [ 216 | | log | 217 | log := 'testFileForTinyLogger.log' asFileReference. 218 | [ 219 | logger addFileLoggerNamed: log basename. 220 | 221 | logger execute: [ ] recordedAs: [ 'this is a test' ]. 222 | 223 | self assert: log exists. 224 | self assert: (log contents includesSubstring: 'this is a test') ] 225 | ensure: [ (log isNotNil and: [ log exists ]) 226 | ifTrue: [ log ensureDelete ] ] 227 | ] 228 | 229 | { #category : 'tests' } 230 | TinyLoggerTest >> testExecuteRecordedAsBlockStillExecuteUserBlockIfNoLoggerIsPresent [ 231 | 232 | | log bool | 233 | bool := false. 234 | log := 'testFileForTinyLogger.log' asFileReference. 235 | [ 236 | logger execute: [ bool := true ] recordedAs: [ 'this is a test' ]. 237 | self assert: bool ] ensure: [ (log isNotNil and: [ log exists ]) ifTrue: [ log ensureDelete ] ] 238 | ] 239 | 240 | { #category : 'tests' } 241 | TinyLoggerTest >> testExecuteRecordedAsKeepRightIndentation [ 242 | 243 | | contents stream errorDetected | 244 | errorDetected := false. 245 | logger 246 | timestampFormatBlock: [ :s | s nextPutAll: 'No time' ]; 247 | removeAllLoggers; 248 | addStdoutLogger. 249 | stream := '' writeStream. 250 | [ 251 | Stdio stub stdout willReturn: stream. 252 | [ Object new execute: [ Error signal: 'test' ] recordedAs: 'This is a new test' ] 253 | on: Error 254 | do: [ errorDetected := true ]. 255 | Object new execute: [ 'test' record ] recordedAs: 'This is a new test'. 256 | contents := Stdio stdout contents asString. 257 | "Ensure we have the right indentation." 258 | self assert: errorDetected. 259 | self assert: contents withUnixLineEndings equals: 'No time: Begin: This is a new test 260 | No time: End with error: This is a new test. Error message: "Error: test" 261 | No time: Begin: This is a new test 262 | No time: test 263 | No time: End: This is a new test 264 | ' withUnixLineEndings ] ensure: [ 265 | Stdio recoverFromGHMutation. 266 | stream close ] 267 | ] 268 | 269 | { #category : 'tests' } 270 | TinyLoggerTest >> testExecuteRecordedAsReturnBlockContent [ 271 | | contents stream bool | 272 | bool := false. 273 | logger 274 | removeAllLoggers; 275 | addStdoutLogger. 276 | stream := '' writeStream. 277 | [ Stdio stub stdout willReturn: stream. 278 | bool := logger execute: [ true ] recordedAs: 'This is a new test'. 279 | contents := Stdio stdout contents asString. 280 | self assert: bool. 281 | self assert: (contents includesSubstring: 'Begin: This is a new test'). 282 | self assert: (contents includesSubstring: 'End: This is a new test') ] 283 | ensure: [ Stdio recoverFromGHMutation. 284 | stream close ] 285 | ] 286 | 287 | { #category : 'tests' } 288 | TinyLoggerTest >> testFileLoggers [ 289 | logger 290 | addTranscriptLogger; 291 | addFileLoggerNamed: 'test.log'; 292 | addFileLoggerNamed: 'test2.log'. 293 | self assert: logger loggers size equals: 3. 294 | self assert: logger fileLoggers size equals: 2. 295 | self assert: (logger fileLoggers allSatisfy: [ :each | each kind = TinyFileLogger kind ]). 296 | self assert: (logger fileLoggers anySatisfy: [ :each | each kind = TinyFileLogger kind and: [ each fileName = 'test.log' ] ]). 297 | self assert: (logger fileLoggers anySatisfy: [ :each | each kind = TinyFileLogger kind and: [ each fileName = 'test2.log' ] ]) 298 | ] 299 | 300 | { #category : 'tests' } 301 | TinyLoggerTest >> testHasLoggers [ 302 | self deny: logger hasLoggers. 303 | logger addFileLogger. 304 | self assert: logger hasLoggers 305 | ] 306 | 307 | { #category : 'tests' } 308 | TinyLoggerTest >> testIndentationString [ 309 | 310 | | contents stream bool | 311 | bool := false. 312 | logger 313 | removeAllLoggers; 314 | addStdoutLogger; 315 | indentationString: '--'. 316 | stream := '' writeStream. 317 | [ 318 | Stdio stub stdout willReturn: stream. 319 | logger execute: [ 'test' record ] recordedAs: 'This is a new test'. 320 | contents := Stdio stdout contents asString. 321 | self assert: (contents includesSubstring: '--test') ] ensure: [ 322 | Stdio recoverFromGHMutation. 323 | stream close ] 324 | ] 325 | 326 | { #category : 'tests' } 327 | TinyLoggerTest >> testLoggers [ 328 | logger 329 | addTranscriptLogger; 330 | addFileLoggerNamed: 'test.log'; 331 | addFileLoggerNamed: 'test2.log'. 332 | self assert: logger loggers size equals: 3. 333 | self assert: (logger loggers anySatisfy: [ :each | each kind = TinyFileLogger kind and: [ each fileName = 'test.log' ] ]). 334 | self assert: (logger loggers anySatisfy: [ :each | each kind = TinyFileLogger kind and: [ each fileName = 'test2.log' ] ]). 335 | self assert: (logger loggers anySatisfy: [ :each | each kind = TinyTranscriptLogger kind ]) 336 | ] 337 | 338 | { #category : 'tests' } 339 | TinyLoggerTest >> testNestedExecuteRecordedAs [ 340 | | contents stream bool1 bool2 | 341 | bool1 := false. 342 | bool2 := false. 343 | logger 344 | removeAllLoggers; 345 | addStdoutLogger. 346 | stream := '' writeStream. 347 | [ Stdio stub stdout willReturn: stream. 348 | logger 349 | execute: [ bool1 := true. 350 | logger execute: [ bool2 := true ] recordedAs: 'Test2' ] 351 | recordedAs: 'Test1'. 352 | contents := Stdio stdout contents asString. 353 | self assert: bool1. 354 | self assert: bool2. 355 | self assert: (contents includesSubstring: 'Begin: Test1'). 356 | self assert: (contents includesSubstring: 'End: Test1'). 357 | self assert: (contents includesSubstring: ' Begin: Test2'). 358 | self assert: (contents includesSubstring: ' End: Test2') ] 359 | ensure: [ Stdio recoverFromGHMutation. 360 | stream close ] 361 | ] 362 | 363 | { #category : 'tests' } 364 | TinyLoggerTest >> testRecord [ 365 | | contents stream | 366 | logger 367 | removeAllLoggers; 368 | addStdoutLogger; 369 | addStdoutLogger; 370 | addTranscriptLogger. 371 | stream := '' writeStream. 372 | [ Stdio stub stdout willReturn: stream. 373 | logger record: 'This is a new test'. 374 | contents := Stdio stdout contents asString lines. 375 | self assert: (contents last includesSubstring: 'This is a new test'). 376 | self assert: ((contents at: contents size - 1) includesSubstring: 'This is a new test') ] 377 | ensure: [ Stdio recoverFromGHMutation. 378 | stream close ] 379 | ] 380 | 381 | { #category : 'tests' } 382 | TinyLoggerTest >> testRecord2 [ 383 | | contents stream | 384 | logger 385 | removeAllLoggers; 386 | addStdoutLogger; 387 | addStdoutLogger; 388 | addTranscriptLogger. 389 | stream := '' writeStream. 390 | [ Stdio stub stdout willReturn: stream. 391 | logger << 'This is a new test'. 392 | contents := Stdio stdout contents asString lines. 393 | self assert: (contents last includesSubstring: 'This is a new test'). 394 | self assert: ((contents at: contents size - 1) includesSubstring: 'This is a new test') ] 395 | ensure: [ Stdio recoverFromGHMutation. 396 | stream close ] 397 | ] 398 | 399 | { #category : 'tests' } 400 | TinyLoggerTest >> testRecord3 [ 401 | | log | 402 | log := 'testFileForTinyLogger.log' asFileReference. 403 | [ 404 | logger addFileLoggerNamed: log basename. 405 | 406 | 'this is a test' record. 407 | 408 | self assert: log exists. 409 | self assert: log contents lines isNotEmpty. 410 | self assert: (log contents lines last includesSubstring: 'this is a test') ] 411 | ensure: [ (log isNotNil and: [ log exists ]) 412 | ifTrue: [ log ensureDelete ] ] 413 | ] 414 | 415 | { #category : 'tests' } 416 | TinyLoggerTest >> testRecordBlock [ 417 | | log | 418 | log := 'testFileForTinyLogger.log' asFileReference. 419 | [ 420 | logger record: [ self fail ]. 421 | 422 | self deny: log exists ] 423 | ensure: [ (log isNotNil and: [ log exists ]) 424 | ifTrue: [ log ensureDelete ] ] 425 | ] 426 | 427 | { #category : 'tests' } 428 | TinyLoggerTest >> testRecordBlock2 [ 429 | | log | 430 | log := 'testFileForTinyLogger.log' asFileReference. 431 | [ 432 | logger addFileLoggerNamed: log basename. 433 | 434 | logger record: [ 'this is a test' ]. 435 | 436 | self assert: log exists. 437 | self assert: (log contents includesSubstring: 'this is a test') ] 438 | ensure: [ (log isNotNil and: [ log exists ]) 439 | ifTrue: [ log ensureDelete ] ] 440 | ] 441 | 442 | { #category : 'tests' } 443 | TinyLoggerTest >> testRecordBlockReceiver [ 444 | | log | 445 | log := 'testFileForTinyLogger.log' asFileReference. 446 | [ 447 | [ self fail ] record. 448 | 449 | self deny: log exists ] 450 | ensure: [ (log isNotNil and: [ log exists ]) 451 | ifTrue: [ log ensureDelete ] ] 452 | ] 453 | 454 | { #category : 'tests' } 455 | TinyLoggerTest >> testRecordBlockReceiver2 [ 456 | | log | 457 | log := 'testFileForTinyLogger.log' asFileReference. 458 | [ 459 | logger addFileLoggerNamed: log basename. 460 | 461 | [ 'this is a test' ] record. 462 | 463 | self assert: log exists. 464 | self assert: (log contents includesSubstring: 'this is a test') ] 465 | ensure: [ (log isNotNil and: [ log exists ]) 466 | ifTrue: [ log ensureDelete ] ] 467 | ] 468 | 469 | { #category : 'tests' } 470 | TinyLoggerTest >> testRemoveAllLoggers [ 471 | logger 472 | addTranscriptLogger; 473 | addFileLoggerNamed: 'test.log'; 474 | addFileLoggerNamed: 'test2.log'. 475 | self assert: logger loggers size equals: 3. 476 | logger removeAllLoggers. 477 | self assert: logger loggers isEmpty 478 | ] 479 | 480 | { #category : 'tests' } 481 | TinyLoggerTest >> testRemoveFileLoggers [ 482 | logger 483 | addTranscriptLogger; 484 | addFileLoggerNamed: 'test.log'; 485 | addFileLoggerNamed: 'test2.log'. 486 | self assert: logger loggers size equals: 3. 487 | logger removeFileLoggers. 488 | self assert: logger loggers size equals: 1 489 | ] 490 | 491 | { #category : 'tests' } 492 | TinyLoggerTest >> testRemoveLogger [ 493 | 494 | | logger1 logger2 | 495 | logger removeAllLoggers. 496 | self assert: logger loggers isEmpty. 497 | 498 | logger 499 | addLogger: (logger1 := TinyFileLogger new); 500 | addLogger: (logger2 := TinyFileLogger named: 'test.log'). 501 | self assert: logger loggers size equals: 2. 502 | 503 | logger removeLogger: logger1. 504 | self assert: logger loggers size equals: 1. 505 | self assert: logger loggers anyOne equals: logger2. 506 | 507 | logger removeLogger: logger2. 508 | self assert: logger loggers isEmpty 509 | ] 510 | 511 | { #category : 'tests' } 512 | TinyLoggerTest >> testRemoveStdoutLoggers [ 513 | logger 514 | addStdoutLogger; 515 | addStdoutLogger; 516 | addFileLoggerNamed: 'test.log'. 517 | self assert: logger loggers size equals: 3. 518 | logger removeStdoutLoggers. 519 | self assert: logger loggers size equals: 1 520 | ] 521 | 522 | { #category : 'tests' } 523 | TinyLoggerTest >> testRemoveTranscriptLoggers [ 524 | logger 525 | addTranscriptLogger; 526 | addTranscriptLogger; 527 | addFileLoggerNamed: 'test.log'. 528 | self assert: logger loggers size equals: 3. 529 | logger removeTranscriptLoggers. 530 | self assert: logger loggers size equals: 1 531 | ] 532 | 533 | { #category : 'tests' } 534 | TinyLoggerTest >> testStdoutLoggers [ 535 | logger 536 | addStdoutLogger; 537 | addStdoutLogger; 538 | addFileLoggerNamed: 'test.log'. 539 | self assert: logger loggers size equals: 3. 540 | self assert: logger stdoutLoggers size equals: 2. 541 | self assert: (logger stdoutLoggers allSatisfy: [ :each | each kind = TinyStdoutLogger kind ]) 542 | ] 543 | 544 | { #category : 'tests' } 545 | TinyLoggerTest >> testTranscriptLoggers [ 546 | logger 547 | addTranscriptLogger; 548 | addTranscriptLogger; 549 | addFileLoggerNamed: 'test.log'. 550 | self assert: logger loggers size equals: 3. 551 | self assert: logger transcriptLoggers size equals: 2. 552 | self assert: (logger transcriptLoggers allSatisfy: [ :each | each kind = TinyTranscriptLogger kind ]) 553 | ] 554 | 555 | { #category : 'tests' } 556 | TinyLoggerTest >> testValueDuringRestoresOriginalLogger [ 557 | "Do not use assert/deny:identicalTo: for compatibility with Pharo 6" 558 | 559 | self assert: TinyCurrentLogger value identicalTo: logger. 560 | 561 | TinyCurrentLogger value: TinyLogger new during: [ self deny: TinyCurrentLogger value identicalTo: logger ]. 562 | self assert: TinyCurrentLogger value identicalTo: logger 563 | ] 564 | 565 | { #category : 'tests' } 566 | TinyLoggerTest >> testWithDefaultLoggers [ 567 | | numberOfDefaultLoggers | 568 | logger withDefaultLoggers. 569 | self assert: logger loggers isNotEmpty. 570 | numberOfDefaultLoggers := logger loggers size. 571 | logger addStdoutLogger. 572 | logger withDefaultLoggers. 573 | "Ensure the loggers are reset when this method is called" 574 | self assert: logger loggers size equals: numberOfDefaultLoggers 575 | ] 576 | 577 | { #category : 'tests' } 578 | TinyLoggerTest >> testindentExecutionBlock [ 579 | | contents stream | 580 | logger 581 | timestampFormatBlock: [ :s | s nextPutAll: 'No time' ]; 582 | removeAllLoggers; 583 | addStdoutLogger; 584 | indentExecutionBlock: true. 585 | stream := '' writeStream. 586 | [ Stdio stub stdout willReturn: stream. 587 | Object new execute: [ 'test' record ] recordedAs: 'This is a new test'. 588 | contents := Stdio stdout contents asString. 589 | 590 | "Ensure we have the right indentation." 591 | self 592 | assert: contents withUnixLineEndings 593 | equals: 594 | 'No time: Begin: This is a new test 595 | No time: test 596 | No time: End: This is a new test 597 | ' withUnixLineEndings ] 598 | ensure: [ Stdio recoverFromGHMutation. 599 | stream close ] 600 | ] 601 | 602 | { #category : 'tests' } 603 | TinyLoggerTest >> testindentExecutionBlockKeepRightIndentation [ 604 | 605 | | contents stream errorDetected | 606 | errorDetected := false. 607 | logger 608 | timestampFormatBlock: [ :s | s nextPutAll: 'No time' ]; 609 | removeAllLoggers; 610 | addStdoutLogger; 611 | indentExecutionBlock: true. 612 | stream := '' writeStream. 613 | [ 614 | Stdio stub stdout willReturn: stream. 615 | [ Object new execute: [ Error signal: 'test' ] recordedAs: 'This is a new test' ] 616 | on: Error 617 | do: [ errorDetected := true ]. 618 | Object new execute: [ 'test' record ] recordedAs: 'This is a new test'. 619 | contents := Stdio stdout contents asString. 620 | "Ensure we have the right indentation." 621 | self assert: errorDetected. 622 | self assert: contents withUnixLineEndings equals: 'No time: Begin: This is a new test 623 | No time: End with error: This is a new test. Error message: "Error: test" 624 | No time: Begin: This is a new test 625 | No time: test 626 | No time: End: This is a new test 627 | ' withUnixLineEndings ] ensure: [ 628 | Stdio recoverFromGHMutation. 629 | stream close ] 630 | ] 631 | --------------------------------------------------------------------------------