├── TestProjects
├── mac-objc-carthage
│ ├── Cartfile
│ ├── Log4swiftTestApp
│ │ ├── main.m
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.m
│ │ ├── Assets.xcassets
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ └── Info.plist
│ ├── test.sh
│ └── Podfile
├── mac-swift-carthage
│ ├── Cartfile
│ ├── Podfile
│ ├── test.sh
│ └── Log4swiftTestApp
│ │ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── Info.plist
│ │ └── AppDelegate.swift
├── iOS+watchOS-swift-carthage
│ ├── Cartfile
│ ├── test.sh.off
│ ├── Log4swiftTestApp WatchKit Extension
│ │ ├── Assets.xcassets
│ │ │ └── Complication.complicationset
│ │ │ │ ├── Circular.imageset
│ │ │ │ └── Contents.json
│ │ │ │ ├── Modular.imageset
│ │ │ │ └── Contents.json
│ │ │ │ ├── Extra Large.imageset
│ │ │ │ └── Contents.json
│ │ │ │ ├── Utilitarian.imageset
│ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ ├── InterfaceController.swift
│ │ ├── Info.plist
│ │ └── ExtensionDelegate.swift
│ ├── Log4swiftTestApp
│ │ ├── ViewController.swift
│ │ ├── Info.plist
│ │ ├── Base.lproj
│ │ │ ├── Main.storyboard
│ │ │ └── LaunchScreen.storyboard
│ │ ├── Assets.xcassets
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ └── AppDelegate.swift
│ └── Log4swiftTestApp WatchKit App
│ │ ├── Base.lproj
│ │ └── Interface.storyboard
│ │ ├── Info.plist
│ │ └── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ └── Contents.json
├── mac-swift-cocoapods
│ ├── Podfile
│ ├── test.sh
│ └── Log4SwiftTestApp
│ │ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── Info.plist
│ │ └── AppDelegate.swift
├── .gitignore
├── mac-objc-cocoapods
│ ├── test.sh
│ ├── Log4SwiftTestApp
│ │ ├── main.m
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.m
│ │ ├── Assets.xcassets
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ └── Info.plist
│ └── Podfile
└── testAll.sh
├── Third parties
└── NSLogger
│ ├── CREDITS
│ ├── LICENSE
│ ├── NSLogger.h
│ └── LoggerCommon.h
├── NOTICE
├── Example playgrounds
├── SoftwareConfiguration-OSX.playground
│ ├── contents.xcplayground
│ └── Contents.swift
└── SimpleUsage-OSX.playground
│ ├── contents.xcplayground
│ └── Contents.swift
├── Log4swift.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── log4swift-OSX.xcscheme
│ ├── log4swiftPerformanceTests.xcscheme
│ └── log4swift-iOS.xcscheme
├── .gitignore
├── .travis.yml
├── Log4swiftTests
├── TestObjects
│ ├── TestRotationPolicy.swift
│ └── MemoryAppender.swift
├── Info.plist
├── Appenders
│ ├── SubclassingTests.swift
│ ├── AppendersRegistryTests.swift
│ ├── SystemAppenderTests.swift
│ └── ASLAppenderTests.swift
├── Utilities
│ ├── XCTestCase+fileAdditions.swift
│ ├── Bool+utilitiesTests.swift
│ ├── XCTestCase+noThrow.swift
│ ├── Array+utilitiesTests.swift
│ ├── FileObserverTests.swift
│ └── String+utilitiesTest.swift
├── Resources
│ └── ValidCompleteConfiguration.plist
├── PatternFormatterTests.swift
├── LogLevelTests.swift
├── RotationPolicy
│ ├── SizeRotationPolicyTests.swift
│ └── DateRotationPolicyTest.swift
├── FunctionalTests.swift
├── Logger+objectiveCTests.swift
└── Logger-AsynchronicityTests.swift
├── Log4swiftPerformanceTests
├── log4swiftPerformanceTests-Info.plist
├── PatternFormatterPerformanceTests.swift
├── FileAppenderPerformanceTests.swift
└── PerformanceTests.swift
├── Info.plist
├── Log4swift
├── Utilities
│ ├── Array+utilities.swift
│ ├── Bool+utilities.swift
│ ├── MultithreadingUtilities.swift
│ ├── Class+utilities.swift
│ ├── FileObserver.swift
│ └── String+utilities.swift
├── Errors.swift
├── Appenders
│ ├── NSLogAppender.swift
│ ├── ASLAppender.swift
│ ├── AppendersRegistry.swift
│ ├── AppleUnifiedLoggerAppender.swift
│ ├── SystemAppender.swift
│ ├── Appender.swift
│ └── NSLoggerAppender.swift
├── LogInformation.swift
├── RotationPolicy
│ ├── FileAppenderRotationPolicy.swift
│ ├── SizeRotationPolicy.swift
│ └── DateRotationPolicy.swift
├── log4swift.h
├── Formatters
│ └── Formatter.swift
├── Objective-c wrappers
│ ├── ASLWrapper.h
│ └── ASLWrapper.m
├── LogLevel.swift
├── Logger+convenience.swift
└── LoggerFactory.swift
├── Log4swift.podspec
└── CHANGELOG.md
/TestProjects/mac-objc-carthage/Cartfile:
--------------------------------------------------------------------------------
1 | git "../../../Log4swift/" "master"
--------------------------------------------------------------------------------
/TestProjects/mac-swift-carthage/Cartfile:
--------------------------------------------------------------------------------
1 | git "../../../Log4swift/" "master"
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/Cartfile:
--------------------------------------------------------------------------------
1 | git "../../../Log4swift/" "master"
--------------------------------------------------------------------------------
/Third parties/NSLogger/CREDITS:
--------------------------------------------------------------------------------
1 | Source files in this folder are parts of the NSLogger project (client side) by Florent Pillet
2 |
3 | https://github.com/fpillet/NSLogger
4 |
--------------------------------------------------------------------------------
/TestProjects/mac-swift-carthage/Podfile:
--------------------------------------------------------------------------------
1 | platform :osx, '10.12'
2 |
3 | target 'Log4SwiftTestApp' do
4 | use_frameworks!
5 | pod 'Log4swift', :path => '../..'
6 | end
7 |
--------------------------------------------------------------------------------
/TestProjects/mac-swift-cocoapods/Podfile:
--------------------------------------------------------------------------------
1 | platform :osx, '10.12'
2 |
3 | target 'Log4swiftTestApp' do
4 | use_frameworks!
5 | pod 'Log4swift', :path => '../..'
6 | end
7 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Log4swift
2 | Copyright © 2015 Jérôme Duquennoy
3 |
4 | This product includes software developed by Florent Pillet for the NSLogger project (client side) : https://github.com/fpillet/NSLogger
5 |
--------------------------------------------------------------------------------
/TestProjects/.gitignore:
--------------------------------------------------------------------------------
1 | # xcworkspaces for the test projects will be created by the different package managers on request, it should not be versioned.
2 | *.xcworkspace
3 | *.err
4 | *.log
5 |
6 | Cartfile.resolved
7 | Carthage
8 |
--------------------------------------------------------------------------------
/Example playgrounds/SoftwareConfiguration-OSX.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Example playgrounds/SimpleUsage-OSX.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Log4swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Log4swift.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/TestProjects/mac-objc-cocoapods/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Clean before building
4 | rm -rf DerivedData 2>/dev/null
5 | rm -rf Pods 2>/dev/null
6 | rm Podfile.lock 2>/dev/null
7 |
8 | pod install
9 | xcodebuild build -workspace Log4swiftTestApp.xcworkspace -scheme Log4swiftTestApp
10 |
--------------------------------------------------------------------------------
/TestProjects/mac-swift-cocoapods/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Clean before building
4 | rm -rf DerivedData 2>/dev/null
5 | rm -rf Pods 2>/dev/null
6 | rm Podfile.lock 2>/dev/null
7 |
8 | pod install
9 | xcodebuild build -workspace Log4swiftTestApp.xcworkspace -scheme Log4swiftTestApp
10 |
--------------------------------------------------------------------------------
/Log4swift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/TestProjects/mac-objc-carthage/Log4swiftTestApp/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // Log4swift-objCTest
4 | //
5 | // Created by Jérôme Duquennoy on 27/10/2015.
6 | // Copyright © 2015 duquennoy. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | int main(int argc, const char * argv[]) {
12 | return NSApplicationMain(argc, argv);
13 | }
14 |
--------------------------------------------------------------------------------
/TestProjects/mac-objc-cocoapods/Log4SwiftTestApp/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // Log4swift-objCTest
4 | //
5 | // Created by Jérôme Duquennoy on 27/10/2015.
6 | // Copyright © 2015 duquennoy. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | int main(int argc, const char * argv[]) {
12 | return NSApplicationMain(argc, argv);
13 | }
14 |
--------------------------------------------------------------------------------
/TestProjects/mac-objc-carthage/Log4swiftTestApp/AppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.h
3 | // Log4swift-objCTest
4 | //
5 | // Created by Jérôme Duquennoy on 27/10/2015.
6 | // Copyright © 2015 duquennoy. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface AppDelegate : NSObject
12 |
13 |
14 | @end
15 |
16 |
--------------------------------------------------------------------------------
/TestProjects/mac-objc-cocoapods/Log4SwiftTestApp/AppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.h
3 | // Log4swift-objCTest
4 | //
5 | // Created by Jérôme Duquennoy on 27/10/2015.
6 | // Copyright © 2015 duquennoy. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface AppDelegate : NSObject
12 |
13 |
14 | @end
15 |
16 |
--------------------------------------------------------------------------------
/TestProjects/mac-objc-carthage/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Clean before building
4 | rm -rf DerivedData 2>/dev/null
5 | rm Cartfile.resolved 2>/dev/null
6 | rm -rf Carthage 2>/dev/null
7 | rm -rf ~/Library/Caches/org.carthage.CarthageKit/dependencies/Log4swift 2>/dev/null
8 |
9 | carthage update --platform mac
10 | xcodebuild build -project Log4swiftTestApp.xcodeproj -scheme Log4swiftTestApp
11 |
--------------------------------------------------------------------------------
/TestProjects/mac-swift-carthage/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Clean before building
4 | rm -rf DerivedData 2>/dev/null
5 | rm Cartfile.resolved 2>/dev/null
6 | rm -rf Carthage 2>/dev/null
7 | rm -rf ~/Library/Caches/org.carthage.CarthageKit/dependencies/Log4swift 2>/dev/null
8 |
9 | carthage update --platform mac
10 | xcodebuild build -project Log4swiftTestApp.xcodeproj -scheme Log4swiftTestApp
11 |
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/test.sh.off:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Clean before building
4 | rm -rf DerivedData 2>/dev/null
5 | rm Cartfile.resolved 2>/dev/null
6 | rm -rf Carthage 2>/dev/null
7 | rm -rf ~/Library/Caches/org.carthage.CarthageKit/dependencies/Log4swift 2>/dev/null
8 |
9 | carthage update --platform iOS
10 | xcodebuild build -project Log4swiftTestApp.xcodeproj -scheme Log4swiftTestApp
11 |
--------------------------------------------------------------------------------
/TestProjects/mac-objc-carthage/Podfile:
--------------------------------------------------------------------------------
1 | platform :osx, '10.12'
2 |
3 | target 'Log4SwiftTestApp' do
4 | use_frameworks!
5 | pod 'Log4swift', :path => '../..'
6 | end
7 |
8 | post_install do |installer|
9 | installer.pods_project.targets.each do |target|
10 | target.build_configurations.each do |config|
11 | config.build_settings['SWIFT_VERSION'] = '4.1'
12 | end
13 | end
14 | end
--------------------------------------------------------------------------------
/TestProjects/mac-objc-cocoapods/Podfile:
--------------------------------------------------------------------------------
1 | platform :osx, '10.12'
2 |
3 | target 'Log4swiftTestApp' do
4 | use_frameworks!
5 | pod 'Log4swift', :path => '../..'
6 | end
7 |
8 | post_install do |installer|
9 | installer.pods_project.targets.each do |target|
10 | target.build_configurations.each do |config|
11 | config.build_settings['SWIFT_VERSION'] = '4.1'
12 | end
13 | end
14 | end
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | .build
7 | *.pbxuser
8 | !default.pbxuser
9 | *.mode1v3
10 | !default.mode1v3
11 | *.mode2v3
12 | !default.mode2v3
13 | *.perspectivev3
14 | !default.perspectivev3
15 | xcuserdata
16 | *.xccheckout
17 | *.moved-aside
18 | DerivedData
19 | *.hmap
20 | *.ipa
21 | *.xcuserstate
22 | *.xcscmblueprint
23 |
24 | # Cocoapods
25 | Podfile.lock
26 | Pods
27 |
28 | Archives
29 |
--------------------------------------------------------------------------------
/TestProjects/testAll.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 |
4 | for folder in `find . -type d -depth 1`
5 | do
6 | echo -n "Testing $folder ... "
7 | pushd "$folder" > /dev/null
8 | rm result.log result.err 2>/dev/null
9 | ./test.sh 1> result.log 2>result.err
10 | result=$?
11 | if [ $result -eq 0 ]; then
12 | echo "SUCCESSS"
13 | else
14 | echo "ERROR, code $result. Please check logs in folder $folder"
15 | fi
16 | popd > /dev/null
17 | done
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/Log4swiftTestApp WatchKit Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "screenWidth" : "{130,145}",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "watch",
10 | "screenWidth" : "{146,165}",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "version" : 1,
16 | "author" : "xcode"
17 | }
18 | }
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/Log4swiftTestApp WatchKit Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "screenWidth" : "{130,145}",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "watch",
10 | "screenWidth" : "{146,165}",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "version" : 1,
16 | "author" : "xcode"
17 | }
18 | }
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/Log4swiftTestApp WatchKit Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "screenWidth" : "{130,145}",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "watch",
10 | "screenWidth" : "{146,165}",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "version" : 1,
16 | "author" : "xcode"
17 | }
18 | }
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/Log4swiftTestApp WatchKit Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "screenWidth" : "{130,145}",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "watch",
10 | "screenWidth" : "{146,165}",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "version" : 1,
16 | "author" : "xcode"
17 | }
18 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | matrix:
2 | include:
3 | - os: osx
4 | osx_image: xcode10.1
5 |
6 | language: objective-c
7 |
8 | before_install:
9 | - brew update
10 | - sudo log config --mode "level:off"
11 | - sudo log config --mode "level:debug" --subsystem Log4swift.tests.systemLoggerAppender
12 | - sudo log config --mode "level:debug" --subsystem -
13 |
14 | script:
15 | - xcodebuild build test -scheme log4swift-OSX
16 | - carthage build --no-skip-current
17 |
18 | branches:
19 | only:
20 | - master
21 |
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/Log4swiftTestApp/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // iOS+watchOS-swift-carthage
4 | //
5 | // Created by Jérôme Duquennoy on 04/08/2018.
6 | // Copyright © 2018 fr.duquennoy. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 | // Do any additional setup after loading the view, typically from a nib.
16 | }
17 |
18 | override func didReceiveMemoryWarning() {
19 | super.didReceiveMemoryWarning()
20 | // Dispose of any resources that can be recreated.
21 | }
22 |
23 |
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/Log4swiftTestApp WatchKit Extension/Assets.xcassets/Complication.complicationset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "assets" : [
3 | {
4 | "idiom" : "watch",
5 | "filename" : "Circular.imageset",
6 | "role" : "circular"
7 | },
8 | {
9 | "idiom" : "watch",
10 | "filename" : "Extra Large.imageset",
11 | "role" : "extra-large"
12 | },
13 | {
14 | "idiom" : "watch",
15 | "filename" : "Modular.imageset",
16 | "role" : "modular"
17 | },
18 | {
19 | "idiom" : "watch",
20 | "filename" : "Utilitarian.imageset",
21 | "role" : "utilitarian"
22 | }
23 | ],
24 | "info" : {
25 | "version" : 1,
26 | "author" : "xcode"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Log4swiftTests/TestObjects/TestRotationPolicy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestRotationPolicy.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 20/08/2018.
6 | // Copyright © 2018 jerome. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Log4swift
11 |
12 | class TestRotationPolicy: FileAppenderRotationPolicy {
13 | public var shouldRotateValue = false
14 | public var appendedData: Data? = nil
15 | public var openedFilePath: String? = nil
16 |
17 | func appenderDidOpenFile(atPath path: String) {
18 | self.openedFilePath = path
19 | }
20 |
21 | func appenderDidAppend(data: Data) {
22 | self.appendedData = data
23 | }
24 |
25 | func shouldRotate() -> Bool {
26 | return self.shouldRotateValue
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Log4swiftTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Log4swiftPerformanceTests/log4swiftPerformanceTests-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/Log4swiftTestApp WatchKit App/Base.lproj/Interface.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Log4swiftTests/Appenders/SubclassingTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SubclassingTests.swift
3 | // Log4swift
4 | //
5 | // Created by Igor Makarov on 03/01/2017.
6 | // Copyright © 2017 jerome. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | // do not import as @testable, we're testing subclassing from outside the module
11 | import Log4swift
12 |
13 | class DummyAppender: Log4swift.Appender {
14 |
15 | override func update(withDictionary dictionary: Dictionary, availableFormatters: Array) throws {
16 | try super.update(withDictionary: dictionary, availableFormatters: availableFormatters)
17 |
18 | }
19 | open override func performLog(_ log: String, level: LogLevel, info: LogInfoDictionary) {
20 | // no need to do anything, just an override
21 | }
22 | }
23 |
24 | class SubclassingTests: XCTestCase {
25 | func testSubclassing() {
26 | let dummyAppender = DummyAppender("dummy")
27 | XCTAssertNotNil(dummyAppender)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/Log4swiftTestApp WatchKit Extension/InterfaceController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InterfaceController.swift
3 | // iOS+watchOS-swift-carthage WatchKit Extension
4 | //
5 | // Created by Jérôme Duquennoy on 04/08/2018.
6 | // Copyright © 2018 fr.duquennoy. All rights reserved.
7 | //
8 |
9 | import WatchKit
10 | import Foundation
11 |
12 |
13 | class InterfaceController: WKInterfaceController {
14 |
15 | override func awake(withContext context: Any?) {
16 | super.awake(withContext: context)
17 |
18 | // Configure interface objects here.
19 | }
20 |
21 | override func willActivate() {
22 | // This method is called when watch view controller is about to be visible to user
23 | super.willActivate()
24 | }
25 |
26 | override func didDeactivate() {
27 | // This method is called when watch view controller is no longer visible
28 | super.didDeactivate()
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.2.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSHumanReadableCopyright
24 | Copyright © 2015 Jérôme Duquennoy. All rights reserved.
25 | NSPrincipalClass
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Log4swift/Utilities/Array+utilities.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+utilities.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 01/07/15.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import Foundation
22 |
23 | extension Array {
24 | public func find(filter:(Array.Iterator.Element) -> Bool) -> Element? {
25 | if let itemIndex = self.firstIndex(where: filter) {
26 | return self[itemIndex]
27 | }
28 | return nil
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/TestProjects/mac-objc-cocoapods/Log4SwiftTestApp/AppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // Log4swift-objCTest
4 | //
5 | // Created by Jérôme Duquennoy on 27/10/2015.
6 | // Copyright © 2015 duquennoy. All rights reserved.
7 | //
8 |
9 | #import "AppDelegate.h"
10 |
11 | @import Log4swift;
12 |
13 | @interface AppDelegate ()
14 |
15 | @property (weak) IBOutlet NSWindow *window;
16 | @end
17 |
18 | @implementation AppDelegate
19 |
20 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
21 | NSError *error = nil;
22 |
23 | NSDictionary *configDictionary = @{
24 | @"RootLogger": @"ping"
25 | };
26 |
27 | if(![LoggerFactory.sharedInstance readConfigurationFromDictionary:configDictionary error:&error]) {
28 | NSLog(@"Failed to load dictionary : %@", error);
29 | } else {
30 | NSLog(@"Successfully loaded dictionary");
31 | }
32 | }
33 |
34 | - (void)applicationWillTerminate:(NSNotification *)aNotification {
35 | // Insert code here to tear down your application
36 | }
37 |
38 | @end
39 |
--------------------------------------------------------------------------------
/Log4swift/Errors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Errors.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 03/07/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import Foundation
22 |
23 | extension NSError {
24 | class func Log4swiftError(description: String) -> NSError {
25 | return NSError(domain: "Log4swift", code: 0, userInfo: [(NSLocalizedFailureReasonErrorKey as String) : description as NSString, (NSLocalizedDescriptionKey as NSString) as String: description as NSString])
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Log4swiftTests/Utilities/XCTestCase+fileAdditions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XCTestCase+noThrow.swift
3 | // Log4swift
4 | //
5 | // Created by jerome on 27/06/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import XCTest
22 |
23 | extension XCTestCase {
24 | func createTemporaryFilePath(fileExtension: String) throws -> String {
25 | let temporaryFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent(UUID().uuidString + "." + fileExtension)
26 | return temporaryFilePath
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/TestProjects/mac-objc-carthage/Log4swiftTestApp/AppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // Log4swift-objCTest
4 | //
5 | // Created by Jérôme Duquennoy on 27/10/2015.
6 | // Copyright © 2015 duquennoy. All rights reserved.
7 | //
8 |
9 | #import "AppDelegate.h"
10 |
11 | @import Log4swift;
12 |
13 | @interface AppDelegate ()
14 |
15 | @property (weak) IBOutlet NSWindow *window;
16 | @end
17 |
18 | @implementation AppDelegate
19 |
20 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
21 | NSError *error = nil;
22 |
23 | NSDictionary *configDictionary = @{
24 | @"RootLogger": @"ping"
25 | };
26 |
27 | if(![LoggerFactory.sharedInstance readConfigurationFromDictionary:configDictionary error:&error]) {
28 | NSLog(@"Failed to load dictionary : %@", error);
29 | } else {
30 | NSLog(@"Successfully loaded dictionary");
31 | }
32 |
33 | // test calls to some methods, to make sure they are still available
34 | (void)[[FileAppender alloc] initWithIdentifier:@"dummyIdentifier" filePath:@"/non/existing/file/path"];
35 | }
36 |
37 | @end
38 |
--------------------------------------------------------------------------------
/TestProjects/mac-objc-carthage/Log4swiftTestApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "size" : "16x16",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "size" : "16x16",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "size" : "32x32",
16 | "scale" : "1x"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "size" : "32x32",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "size" : "128x128",
26 | "scale" : "1x"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "size" : "128x128",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "size" : "256x256",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "size" : "256x256",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "size" : "512x512",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "size" : "512x512",
51 | "scale" : "2x"
52 | }
53 | ],
54 | "info" : {
55 | "version" : 1,
56 | "author" : "xcode"
57 | }
58 | }
--------------------------------------------------------------------------------
/TestProjects/mac-objc-cocoapods/Log4SwiftTestApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "size" : "16x16",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "size" : "16x16",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "size" : "32x32",
16 | "scale" : "1x"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "size" : "32x32",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "size" : "128x128",
26 | "scale" : "1x"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "size" : "128x128",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "size" : "256x256",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "size" : "256x256",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "size" : "512x512",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "size" : "512x512",
51 | "scale" : "2x"
52 | }
53 | ],
54 | "info" : {
55 | "version" : 1,
56 | "author" : "xcode"
57 | }
58 | }
--------------------------------------------------------------------------------
/TestProjects/mac-swift-carthage/Log4swiftTestApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "size" : "16x16",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "size" : "16x16",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "size" : "32x32",
16 | "scale" : "1x"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "size" : "32x32",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "size" : "128x128",
26 | "scale" : "1x"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "size" : "128x128",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "size" : "256x256",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "size" : "256x256",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "size" : "512x512",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "size" : "512x512",
51 | "scale" : "2x"
52 | }
53 | ],
54 | "info" : {
55 | "version" : 1,
56 | "author" : "xcode"
57 | }
58 | }
--------------------------------------------------------------------------------
/TestProjects/mac-swift-cocoapods/Log4SwiftTestApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "size" : "16x16",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "size" : "16x16",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "size" : "32x32",
16 | "scale" : "1x"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "size" : "32x32",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "size" : "128x128",
26 | "scale" : "1x"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "size" : "128x128",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "size" : "256x256",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "size" : "256x256",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "size" : "512x512",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "size" : "512x512",
51 | "scale" : "2x"
52 | }
53 | ],
54 | "info" : {
55 | "version" : 1,
56 | "author" : "xcode"
57 | }
58 | }
--------------------------------------------------------------------------------
/TestProjects/mac-swift-carthage/Log4swiftTestApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSHumanReadableCopyright
28 | Copyright © 2016 dxo. All rights reserved.
29 | NSMainNibFile
30 | MainMenu
31 | NSPrincipalClass
32 | NSApplication
33 |
34 |
35 |
--------------------------------------------------------------------------------
/TestProjects/mac-swift-cocoapods/Log4SwiftTestApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSHumanReadableCopyright
28 | Copyright © 2016 dxo. All rights reserved.
29 | NSMainNibFile
30 | MainMenu
31 | NSPrincipalClass
32 | NSApplication
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Log4swift/Appenders/NSLogAppender.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSLogAppender.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 29/07/15.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import Foundation
22 |
23 | /**
24 | The NSLog appender uses NSLog to issue the logs. It is not extermely performant, you should only use it if you want to have the exact same behavior as NSLog (same formatting, output to stderr of ALS depending on the situation, ...)
25 | */
26 | public class NSLogAppender: Appender {
27 | public override func performLog(_ log: String, level: LogLevel, info: LogInfoDictionary) {
28 | NSLog(log)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/TestProjects/mac-objc-carthage/Log4swiftTestApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSHumanReadableCopyright
28 | Copyright © 2015 duquennoy. All rights reserved.
29 | NSMainNibFile
30 | MainMenu
31 | NSPrincipalClass
32 | NSApplication
33 |
34 |
35 |
--------------------------------------------------------------------------------
/TestProjects/mac-objc-cocoapods/Log4SwiftTestApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSHumanReadableCopyright
28 | Copyright © 2015 duquennoy. All rights reserved.
29 | NSMainNibFile
30 | MainMenu
31 | NSPrincipalClass
32 | NSApplication
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Example playgrounds/SimpleUsage-OSX.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | //: Playground - noun: a place where people can play
2 |
3 | import Log4swift
4 |
5 | // This playground demonstrates the simplest possible use of Log4Swift.
6 | // This approach is only interesting if you are working on a project that is not meant to grow big :
7 | // logs cannot be sorted by categories, which might make them much less usefull if you have lots of them.
8 |
9 | // You can issue a log in one simple line.
10 | // This uses the default root logger, that will print your log message to the standard output using NSLog.
11 | Logger.info("Hello world !")
12 |
13 | // You can change the threshold level when using this simple method by accessing the root logger :
14 | LoggerFactory.sharedInstance.rootLogger.thresholdLevel = .Warning
15 | Logger.info("This log will be ignored")
16 | Logger.warning("This log will be issued to stdout")
17 | Logger.error("This log will be issued to stderr")
18 |
19 | // Log with closures allow you to avoid running message composition code if log is blocked by thresholds
20 | LoggerFactory.sharedInstance.rootLogger.thresholdLevel = .Warning
21 | Logger.info {
22 | return "This closure will not be executed"
23 | }
24 | Logger.warning {
25 | return "This closure will be executed"
26 | }
27 |
--------------------------------------------------------------------------------
/Log4swift/LogInformation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LogInformation.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 08/07/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | /**
22 | Keys used in the information dictionary attached to log messages
23 | */
24 | public enum LogInfoKeys {
25 | case LogLevel
26 | case LoggerName
27 | case FileName
28 | case FileLine
29 | case Function
30 | case Timestamp
31 | case ThreadId
32 | case ThreadName
33 | }
34 |
35 | /**
36 | The definition of the type used to attach meta informations to log messages
37 | */
38 | public typealias LogInfoDictionary = Dictionary
39 |
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/Log4swiftTestApp WatchKit App/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | iOS+watchOS-swift-carthage WatchKit App
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | UISupportedInterfaceOrientations
24 |
25 | UIInterfaceOrientationPortrait
26 | UIInterfaceOrientationPortraitUpsideDown
27 |
28 | WKCompanionAppBundleIdentifier
29 | fr.duquennoy.Log4swiftTestApp
30 | WKWatchKitApp
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Log4swift/RotationPolicy/FileAppenderRotationPolicy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileAppenderRotationPolicy.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 20/08/2018.
6 | // Copyright © 2018 jerome. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | Implement this protocol to create an object that controls when files
13 | are rotated by the FileAppender.
14 | */
15 | public protocol FileAppenderRotationPolicy {
16 | /// Will be called when the file appender opens a destination file.
17 | /// You can use this hook to collect data on the file.
18 | func appenderDidOpenFile(atPath path: String)
19 |
20 | /// Will be called after a message has be added to the current file.
21 | /// You can use this hook to update the data you maintain on the file.
22 | /// Make sure your implementation of this method is as lightweight
23 | /// as possible, as it will be called for every single log message.
24 | func appenderDidAppend(data: Data)
25 |
26 | /// Will be called before logging to the file. If this method returns
27 | /// true, then the log file will be rotated before logging.
28 | /// Make sure your implementation of this method is as lightweight
29 | /// as possible, as it will be called for every single log message.
30 | func shouldRotate() -> Bool
31 | }
32 |
--------------------------------------------------------------------------------
/Log4swift/RotationPolicy/SizeRotationPolicy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SizeRotationPolicy.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 20/08/2018.
6 | // Copyright © 2018 jerome. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | This rotation policy will request a rotation once a file gets bigger than a given threshold.
13 | */
14 | class SizeRotationPolicy: FileAppenderRotationPolicy {
15 | /// The maximum size of the file in octets before rotation is requested.
16 | public var maxFileSize: UInt64
17 | private var currentFileSize: UInt64 = 0
18 |
19 | /// Creates a rotation policy with a max file size in octet.
20 | init(maxFileSize: UInt64) {
21 | self.maxFileSize = maxFileSize
22 | }
23 |
24 | func appenderDidOpenFile(atPath path: String) {
25 | let fileManager = FileManager.default
26 | do {
27 | let fileAttributes = try fileManager.attributesOfItem(atPath: path)
28 | self.currentFileSize = fileAttributes[FileAttributeKey.size] as? UInt64 ?? 0
29 | } catch {
30 | self.currentFileSize = 0
31 | }
32 | }
33 |
34 | func appenderDidAppend(data: Data) {
35 | self.currentFileSize += UInt64(data.count)
36 | }
37 |
38 | func shouldRotate() -> Bool {
39 | return self.currentFileSize > self.maxFileSize
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Log4swift/log4swift.h:
--------------------------------------------------------------------------------
1 | //
2 | // log4swift.h
3 | // log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 14/06/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import
22 |
23 | //! Project version number for log4swift.
24 | FOUNDATION_EXPORT double log4swiftVersionNumber;
25 |
26 | //! Project version string for log4swift.
27 | FOUNDATION_EXPORT const unsigned char log4swiftVersionString[];
28 |
29 | // In this header, you should import all the public headers of your framework using statements like #import
30 | #if !TARGET_OS_WATCH
31 | #import "NSLogger.h"
32 | #import "LoggerClient.h"
33 | #import "LoggerCommon.h"
34 | #endif
35 | #import "ASLWrapper.h"
36 |
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/Log4swiftTestApp WatchKit App/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "24x24",
5 | "idiom" : "watch",
6 | "scale" : "2x",
7 | "role" : "notificationCenter",
8 | "subtype" : "38mm"
9 | },
10 | {
11 | "size" : "27.5x27.5",
12 | "idiom" : "watch",
13 | "scale" : "2x",
14 | "role" : "notificationCenter",
15 | "subtype" : "42mm"
16 | },
17 | {
18 | "size" : "29x29",
19 | "idiom" : "watch",
20 | "role" : "companionSettings",
21 | "scale" : "2x"
22 | },
23 | {
24 | "size" : "29x29",
25 | "idiom" : "watch",
26 | "role" : "companionSettings",
27 | "scale" : "3x"
28 | },
29 | {
30 | "size" : "40x40",
31 | "idiom" : "watch",
32 | "scale" : "2x",
33 | "role" : "appLauncher",
34 | "subtype" : "38mm"
35 | },
36 | {
37 | "size" : "86x86",
38 | "idiom" : "watch",
39 | "scale" : "2x",
40 | "role" : "quickLook",
41 | "subtype" : "38mm"
42 | },
43 | {
44 | "size" : "98x98",
45 | "idiom" : "watch",
46 | "scale" : "2x",
47 | "role" : "quickLook",
48 | "subtype" : "42mm"
49 | }
50 | ],
51 | "info" : {
52 | "version" : 1,
53 | "author" : "xcode"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/Log4swiftTestApp WatchKit Extension/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | iOS+watchOS-swift-carthage WatchKit Extension
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | XPC!
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | NSExtension
24 |
25 | NSExtensionAttributes
26 |
27 | WKAppBundleIdentifier
28 | fr.duquennoy.Log4swiftTestApp.watchkitapp
29 |
30 | NSExtensionPointIdentifier
31 | com.apple.watchkit
32 |
33 | WKExtensionDelegateClassName
34 | $(PRODUCT_MODULE_NAME).ExtensionDelegate
35 |
36 |
37 |
--------------------------------------------------------------------------------
/Log4swift.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "Log4swift"
3 | s.version = "1.2.0"
4 | s.summary = "A looging library written in swift."
5 |
6 | s.description = <<-DESC
7 | Log4swift is a logging library similar in philosophy to log4j.
8 | It is meant to be :
9 |
10 | * very simple to use for simple cases
11 | * extensively configurable for less simple cases
12 | * taking advantage of the swift 2 language
13 |
14 | DESC
15 |
16 | s.homepage = "http://github.com/jduquennoy/Log4swift"
17 |
18 | s.license = { :type => "Apache v2.0", :file => "LICENSE" }
19 |
20 | s.author = { "Jerome Duquennoy" => "jerome@duquennoy.fr" }
21 |
22 | s.ios.deployment_target = "8.0"
23 | s.watchos.deployment_target = "2.0"
24 | s.osx.deployment_target = "10.10"
25 | s.swift_versions = ['4.0', '4.1', '4.2', '5.0']
26 |
27 | s.source = { :git => "https://github.com/jduquennoy/Log4swift.git", :tag => "v1.2.0" }
28 |
29 | s.source_files = "Log4swift", "Log4swift/**/*.{swift,h,m}", "Third parties/**/*.{h,m}"
30 |
31 | s.public_header_files = ["Log4swift/log4swift.h", "Third Parties/NSLogger/*.h", "Log4swift/Objective-c wrappers/*.h"]
32 |
33 | s.watchos.exclude_files = ["Third Parties/NSLogger/*", "Log4swift/Appenders/NSLoggerAppender.swift"]
34 | end
35 |
--------------------------------------------------------------------------------
/Log4swift/Utilities/Bool+utilities.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bool.utilities.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 14/06/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | /**
22 | Extends Bool to add the ability to initialize with a string value.
23 | */
24 | extension Bool {
25 | /// Returns true if the value is "true" or "yes" (case insensitive compare).
26 | /// This initializer accepts an optional string, and will return false if the optional is not set.
27 | public init(_ stringValue: String?) {
28 | if let stringValue = stringValue {
29 | switch(stringValue.lowercased()) {
30 | case "true", "yes":
31 | self = true
32 | default:
33 | self = false
34 | }
35 | } else {
36 | self = false
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Log4swift/Formatters/Formatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Formatter.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 18/06/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | /**
22 | This protocol defines a formatter, that will format log messages on the fly.
23 | */
24 | public protocol Formatter {
25 | /// A string identifier that should uniquely identify a formatter.
26 | var identifier: String { get }
27 |
28 | init (_ identifier: String)
29 |
30 | func update(withDictionary dictionary: Dictionary) throws
31 |
32 | /// Formats the given message, using the provided info dictionary.
33 | /// Info dictionary contains additional infos that can be rendered as a string and that can be used by matchers.
34 | func format(message: String, info: LogInfoDictionary) -> String
35 | }
36 |
--------------------------------------------------------------------------------
/Log4swift/RotationPolicy/DateRotationPolicy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateRotationPolicy.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 20/08/2018.
6 | // Copyright © 2018 jerome. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | This rotation policy will request a rotation once a file gets older than a given threshold.
13 | */
14 | class DateRotationPolicy: FileAppenderRotationPolicy {
15 | /// The maximum age of the file in seconds before rotation is requested.
16 | public var maxFileAge: TimeInterval
17 | internal var currentFileCreationCreationDate: Date? = nil
18 |
19 | /// Creates a rotation policy with a max file age in seconds.
20 | init(maxFileAge: TimeInterval) {
21 | self.maxFileAge = maxFileAge
22 | }
23 |
24 | func appenderDidOpenFile(atPath path: String) {
25 | let fileManager = FileManager.default
26 | do {
27 | let fileAttributes = try fileManager.attributesOfItem(atPath: path)
28 | self.currentFileCreationCreationDate = fileAttributes[FileAttributeKey.creationDate] as? Date ?? Date()
29 | } catch {
30 | self.currentFileCreationCreationDate = Date()
31 | }
32 | }
33 |
34 | func appenderDidAppend(data: Data) {
35 | }
36 |
37 | func shouldRotate() -> Bool {
38 | guard let fileDate = self.currentFileCreationCreationDate else { return false }
39 |
40 | return Date().timeIntervalSince(fileDate) >= self.maxFileAge
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Log4swiftTests/Utilities/Bool+utilitiesTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bool+utilitiesTests.swift
3 | // Log4swift
4 | //
5 | // Created by jduquennoy on 30/06/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import XCTest
22 | import Log4swift
23 |
24 | class BoolUtilitiesTests: XCTestCase {
25 |
26 | func testBoolParsesYesOrTrueAsTrueCaseInsensitively() {
27 | XCTAssertTrue(Bool("yes"))
28 | XCTAssertTrue(Bool("YES"))
29 | XCTAssertTrue(Bool("true"))
30 | XCTAssertTrue(Bool("TruE"))
31 | }
32 |
33 | func testBoolParsesNoOrFalseAsFalseCaseInsensitively() {
34 | XCTAssertFalse(Bool("no"))
35 | XCTAssertFalse(Bool("nO"))
36 | XCTAssertFalse(Bool("false"))
37 | XCTAssertFalse(Bool("fALse"))
38 | }
39 |
40 | func testBoolParsesNilStringAsFalse() {
41 | XCTAssertFalse(Bool((nil as String?)))
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/Log4swiftTests/Utilities/XCTestCase+noThrow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XCTestCase+noThrow.swift
3 | // Log4swift
4 | //
5 | // Created by jerome on 27/06/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import XCTest
22 |
23 | extension XCTestCase {
24 |
25 | func XCTAssertThrows(_ file: StaticString = #file, line: UInt = #line, _ closure:() throws -> Void) {
26 | do {
27 | try closure()
28 | XCTFail("Closure did not throw an error", file: file, line: line)
29 | } catch {
30 | // expected, nothing to do
31 | }
32 | }
33 |
34 | func XCTAssertNoThrow(_ file: StaticString = #file, line: UInt = #line, _ closure:() throws -> T) -> T? {
35 | do {
36 | return try closure()
37 | } catch let error {
38 | XCTFail("Closure throw unexpected error \(error)", file: file, line: line)
39 | }
40 | return nil
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/Log4swift/Objective-c wrappers/ASLWrapper.h:
--------------------------------------------------------------------------------
1 | //
2 | // ASLWrapper.h
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 29/07/15.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | @import Foundation;
22 |
23 | /**
24 | This wrapper makes it possible to use ASL in the swift code.
25 | That is not possible otherwise because the asl.h header is not modular, and thus cannot be imported in the log4swift.h file.
26 | This wrapper will delegate all log operations to a serial queue to avoid concurrency problems with ASL.
27 | */
28 | @interface ASLWrapper : NSObject
29 |
30 | - (nonnull instancetype)init;
31 |
32 | - (void)logMessage:(nonnull NSString *)log level:(int)level category:(nonnull NSString *)category;
33 |
34 | - (int)getLevelOfMessageMatchingText:(nonnull NSString *)message;
35 | - (nullable NSString *)getFacilityOfMessageMatchingText:(nonnull NSString *)message;
36 | @end
37 |
--------------------------------------------------------------------------------
/Log4swift/Appenders/ASLAppender.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ASLAppender.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 29/07/15.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import Foundation
22 |
23 | /**
24 | This appender will send messages to ASL, the Apple System Log.
25 | */
26 | public class ASLAppender : Appender {
27 | internal let aslClient = ASLWrapper ()
28 |
29 | @objc
30 | required public init(_ identifier: String) {
31 | super.init(identifier)
32 | }
33 |
34 | public override func performLog(_ log: String, level: LogLevel, info: LogInfoDictionary) {
35 | let category: String
36 | if let categoryFromInfo = info[LogInfoKeys.LoggerName] {
37 | category = categoryFromInfo.description
38 | } else {
39 | category = "Undefined"
40 | }
41 | self.aslClient.logMessage(log, level: Int32(level.rawValue), category: category)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Log4swiftTests/Appenders/AppendersRegistryTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppendersRegistry.swift
3 | // log4swiftTests
4 | //
5 | // Created by Jérome Duquennoy on 29/03/2020.
6 | // Copyright © 2020 jerome. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Log4swift
11 |
12 | class AppendersRegistryTests: XCTestCase {
13 |
14 | func testRegistryKnowsIncludedAppendersByDefault() {
15 | XCTAssertNotNil(AppendersRegistry.appenderForClassName("StdOutAppender"))
16 | XCTAssertNotNil(AppendersRegistry.appenderForClassName("FileAppender"))
17 | XCTAssertNotNil(AppendersRegistry.appenderForClassName("NSLoggerAppender"))
18 | XCTAssertNotNil(AppendersRegistry.appenderForClassName("NSLogAppender"))
19 | XCTAssertNotNil(AppendersRegistry.appenderForClassName("ASLAppender"))
20 | XCTAssertNotNil(AppendersRegistry.appenderForClassName("SystemAppender"))
21 | XCTAssertNotNil(AppendersRegistry.appenderForClassName("AppleUnifiedLoggerAppender"))
22 | }
23 |
24 | func testRegistryKnowsCustomAppendersOnceRegistered() {
25 | AppendersRegistry.registerAppender(MemoryAppender.self)
26 |
27 | XCTAssertNotNil(AppendersRegistry.appenderForClassName("MemoryAppender"))
28 | }
29 |
30 | func testRegistryAppendersMatchingIsCaseInsensitive() {
31 | XCTAssertNotNil(AppendersRegistry.appenderForClassName("stDoutapPEnder"))
32 | XCTAssert(
33 | AppendersRegistry.appenderForClassName("StdOutAppender") === AppendersRegistry.appenderForClassName("stDoutapPEnder")
34 | )
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Log4swiftTests/Resources/ValidCompleteConfiguration.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Formatters
6 |
7 |
8 | Pattern
9 | log pattern - %m
10 | Identifier
11 | patternFormatter1
12 | Class
13 | PatternFormatter
14 |
15 |
16 | Appenders
17 |
18 |
19 | FormatterId
20 | patternFormatter1
21 | Class
22 | StdOutAppender
23 | Identifier
24 | stdOutAppender1
25 | ThresholdLevel
26 | Warning
27 |
28 |
29 | RootLogger
30 |
31 | ThresholdLevel
32 | Info
33 |
34 | Loggers
35 |
36 |
37 | Identifier
38 | project.feature.logger1
39 | ThresholdLevel
40 | Error
41 | AppenderIds
42 |
43 | stdOutAppender1
44 |
45 |
46 |
47 | Identifier
48 | project.feature.logger2
49 | ThresholdLevel
50 | Fatal
51 | AppenderIds
52 |
53 | stdOutAppender1
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/Log4swiftTests/Utilities/Array+utilitiesTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+utilitiesTests.swift
3 | // Log4swift
4 | //
5 | // Created by dxo on 01/07/15.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import XCTest
22 |
23 | class ArrayUtilitiesTests: XCTestCase {
24 |
25 | func testFindReturnsFirstItemMatchingFilter() {
26 | let var1 = "ping1"
27 | let var2 = "pong"
28 | let var3 = "ping2"
29 |
30 | let array = [var1, var2, var3]
31 |
32 | // Execute
33 | let foundItem = array.find{ $0.hasPrefix("ping") }
34 |
35 | // Validate
36 | XCTAssertTrue(foundItem == var1)
37 | }
38 |
39 | func testFindReturnsNilIfItemIsNotFound() {
40 | let var1 = "ping"
41 | let var2 = "pong"
42 | let var3 = "p1ng"
43 |
44 | let array = [var1, var2, var3]
45 |
46 | // Execute
47 | let foundItem = array.find{ $0 == "notExisting" }
48 |
49 | // Validate
50 | XCTAssert(foundItem == nil)
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/Third parties/NSLogger/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 1998, Regents of the University of California
2 | All rights reserved.
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are met:
5 |
6 | * Redistributions of source code must retain the above copyright
7 | notice, this list of conditions and the following disclaimer.
8 | * Redistributions in binary form must reproduce the above copyright
9 | notice, this list of conditions and the following disclaimer in the
10 | documentation and/or other materials provided with the distribution.
11 | * Neither the name of the University of California, Berkeley nor the
12 | names of its contributors may be used to endorse or promote products
13 | derived from this software without specific prior written permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
16 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/Log4swiftTestApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Log4swiftTests/PatternFormatterTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PatternFormatterTests.swift
3 | // Log4swift
4 | //
5 | // Created by jduquennoy on 19/06/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import XCTest
22 |
23 | class PatternFormatterTests: XCTestCase {
24 |
25 | override func setUp() {
26 | super.setUp()
27 | // Put setup code here. This method is called before the invocation of each test method in the class.
28 | }
29 |
30 | override func tearDown() {
31 | // Put teardown code here. This method is called after the invocation of each test method in the class.
32 | super.tearDown()
33 | }
34 |
35 | func testExample() {
36 | // This is an example of a functional test case.
37 | // Use XCTAssert and related functions to verify your tests produce the correct results.
38 | }
39 |
40 | func testPerformanceExample() {
41 | // This is an example of a performance test case.
42 | self.measureBlock() {
43 | // Put the code you want to measure the time of here.
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/Log4swiftTestApp/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Log4swiftTests/TestObjects/MemoryAppender.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MemoryAppender.swift
3 | // Log4swift
4 | //
5 | // Created by jerome on 14/06/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import Foundation
22 | @testable import Log4swift
23 |
24 | typealias LoggedMessage = (message: String, level: LogLevel)
25 |
26 | /**
27 | This test appender will store logs in memory for latter validation.
28 | It can also add a delay when logging messages.
29 | */
30 | class MemoryAppender: Appender {
31 | var loggingDelay: TimeInterval? = nil
32 | var logMessages = [LoggedMessage]()
33 |
34 | init() {
35 | super.init("test.memoryAppender")
36 | }
37 |
38 | required init(_ identifier: String) {
39 | super.init(identifier)
40 | }
41 |
42 | override func performLog(_ log: String, level: LogLevel, info: LogInfoDictionary) {
43 | if let loggingDelay = self.loggingDelay {
44 | Thread.sleep(forTimeInterval: loggingDelay)
45 | }
46 | logMessages.append((message: log, level: level))
47 | }
48 |
49 | }
50 |
51 | func ==(left: LoggedMessage, right: LoggedMessage) -> Bool {
52 | return (left.message == right.message) && (left.level == right.level)
53 | }
54 |
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/Log4swiftTestApp/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Log4swift/Appenders/AppendersRegistry.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppendersRepository.swift
3 | // Log4swift
4 | //
5 | // Created by Jérome Duquennoy on 29/03/2020.
6 | // Copyright © 2020 jerome. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// A registry that contains the list of appender types that can be used when loading
12 | /// a configuration from a file.
13 | public struct AppendersRegistry {
14 | /// The actual list of appenders available
15 | internal static var appenders: [Appender.Type] = {
16 | var appenders = [
17 | StdOutAppender.self,
18 | FileAppender.self,
19 | NSLogAppender.self,
20 | ASLAppender.self,
21 | SystemAppender.self
22 | ]
23 | #if !os(watchOS)
24 | appenders.append(NSLoggerAppender.self)
25 | #endif
26 | if #available(iOS 10.0, macOS 10.12, watchOS 3, *) {
27 | appenders.append(AppleUnifiedLoggerAppender.self)
28 | }
29 | return appenders
30 | } ()
31 |
32 | /// Returns an appender type for a class name.
33 | /// The search is case insensitive.
34 | internal static func appenderForClassName(_ className: String) -> Appender.Type? {
35 | let classNameLowercased = className.lowercased()
36 |
37 | for appenderType in Appender.availableAppenderTypes {
38 | if String(describing: appenderType).lowercased() == classNameLowercased {
39 | return appenderType
40 | }
41 | }
42 |
43 | return nil
44 | }
45 |
46 |
47 | /// Add an appender type to the registry.
48 | /// That appender can then be created from configuration file, specifying its class name in the Class parameter.
49 | /// You do not need to register custom appenders used to configure your system programmatically.
50 | public static func registerAppender(_ newAppender: Appender.Type) {
51 | self.appenders.append(newAppender)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Log4swift/Utilities/MultithreadingUtilities.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MultithreadingUtilities.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 07/08/2018.
6 | // Copyright © 2018 jerome. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | internal final class PThreadMutex {
12 | public typealias MutexPrimitive = pthread_mutex_t
13 |
14 | public enum PThreadMutexType {
15 | case normal // PTHREAD_MUTEX_NORMAL
16 | case recursive // PTHREAD_MUTEX_RECURSIVE
17 | }
18 |
19 | public var unsafeMutex = pthread_mutex_t()
20 |
21 | /// - parameter type: wether the mutex is a normal or recursive one. Default value is normal.
22 | public init(type: PThreadMutexType = .normal) {
23 | var attr = pthread_mutexattr_t()
24 | guard pthread_mutexattr_init(&attr) == 0 else {
25 | preconditionFailure()
26 | }
27 | switch type {
28 | case .normal:
29 | pthread_mutexattr_settype(&attr, Int32(PTHREAD_MUTEX_NORMAL))
30 | case .recursive:
31 | pthread_mutexattr_settype(&attr, Int32(PTHREAD_MUTEX_RECURSIVE))
32 | }
33 | guard pthread_mutex_init(&unsafeMutex, &attr) == 0 else {
34 | preconditionFailure()
35 | }
36 | pthread_mutexattr_destroy(&attr)
37 | }
38 |
39 | deinit {
40 | pthread_mutex_destroy(&unsafeMutex)
41 | }
42 |
43 | public func unbalancedLock() {
44 | pthread_mutex_lock(&unsafeMutex)
45 | }
46 |
47 | public func unbalancedTryLock() -> Bool {
48 | return pthread_mutex_trylock(&unsafeMutex) == 0
49 | }
50 |
51 | public func unbalancedUnlock() {
52 | pthread_mutex_unlock(&unsafeMutex)
53 | }
54 | }
55 |
56 | internal extension PThreadMutex {
57 | /// Executes a closure as a critical section (not executable concurrently by different threads).
58 | func sync(execute: () throws -> R) rethrows -> R {
59 | self.unbalancedLock()
60 | defer { self.unbalancedUnlock() }
61 | return try execute()
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Log4swift/Utilities/Class+utilities.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClassInfo.swift
3 | // Log4swift
4 | //
5 | // Created by Igor Makarov on 17/05/2017.
6 | // Copyright © 2017 jerome. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import Foundation
22 |
23 | internal struct ClassInfo {
24 | let classObject: AnyClass
25 |
26 | init(_ classObject: AnyClass) {
27 | self.classObject = classObject
28 | }
29 |
30 | var superclassInfo: ClassInfo? {
31 | if let superclassObject: AnyClass = class_getSuperclass(self.classObject) {
32 | return ClassInfo(superclassObject)
33 | }
34 | return nil
35 | }
36 |
37 | public func isSubclass(of cls: ClassInfo) -> Bool {
38 | if let superclass = self.superclassInfo {
39 | return superclass.classObject == cls.classObject || superclass.isSubclass(of: cls)
40 | }
41 | return false
42 | }
43 |
44 | public var subclasses: [ClassInfo] {
45 | var subclassList = [ClassInfo]()
46 |
47 | var count = UInt32(0)
48 | let classList = UnsafePointer(objc_copyClassList(&count))!
49 |
50 | for i in 0.. OSLog {
45 | let osLog: OSLog
46 | if let osLogFromCache = self.loggerToOSLogCache[loggerName] {
47 | osLog = osLogFromCache
48 | } else {
49 | let subsystem = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String ?? "-"
50 | osLog = OSLog(subsystem: subsystem, category: loggerName)
51 | self.loggerToOSLogCache[loggerName] = osLog
52 | }
53 |
54 | return osLog
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/Log4swiftTestApp/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // iOS+watchOS-swift-carthage
4 | //
5 | // Created by Jérôme Duquennoy on 04/08/2018.
6 | // Copyright © 2018 fr.duquennoy. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/Log4swiftTests/RotationPolicy/SizeRotationPolicyTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SizeRotationPolicyTests.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 20/08/2018.
6 | // Copyright © 2018 jerome. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import XCTest
11 | @testable import Log4swift
12 |
13 | class SizeRotationPolicyTests: XCTestCase {
14 |
15 | func testRotationIsRequestedWhenLoggedDataExceedsMaxSize() throws {
16 | let rotationPolicy = SizeRotationPolicy(maxFileSize: 100)
17 | let logMessageData = "test message".data(using: .utf8)!
18 | var logsCount = 0
19 |
20 | // Execute
21 | repeat {
22 | rotationPolicy.appenderDidAppend(data: logMessageData)
23 | logsCount += 1
24 | } while !rotationPolicy.shouldRotate() && logsCount < 1000
25 |
26 | // Validate
27 | XCTAssertEqual(logsCount, 100/logMessageData.count + 1)
28 | }
29 |
30 | func testPolicyConsidersNullSizeIfOpenFileIsNotReadable() throws {
31 | let rotationPolicy = SizeRotationPolicy(maxFileSize: 100)
32 | let logMessageData = "test message".data(using: .utf8)!
33 | var logsCount = 0
34 |
35 | // Execute
36 | rotationPolicy.appenderDidOpenFile(atPath: "/file/that/does/not/exist.log")
37 | repeat {
38 | rotationPolicy.appenderDidAppend(data: logMessageData)
39 | logsCount += 1
40 | } while !rotationPolicy.shouldRotate() && logsCount < 1000
41 |
42 | // Validate
43 | XCTAssertEqual(logsCount, 100/logMessageData.count + 1)
44 | }
45 |
46 | func testInitialFileSizeIsTakenIntoAccount() throws {
47 | let filePath = try self.createTemporaryFilePath(fileExtension: "log")
48 | let initialFileContent = "this is the initial file content.\n".data(using: .utf8)!
49 | try initialFileContent.write(to: URL.init(fileURLWithPath: filePath))
50 | let rotationPolicy = SizeRotationPolicy(maxFileSize: UInt64(initialFileContent.count.advanced(by: 10)))
51 | let logMessageData = "a".data(using: .utf8)!
52 | var logsCount = 0
53 |
54 | // Execute
55 | rotationPolicy.appenderDidOpenFile(atPath: filePath)
56 | repeat {
57 | rotationPolicy.appenderDidAppend(data: logMessageData)
58 | logsCount += 1
59 | } while !rotationPolicy.shouldRotate() && logsCount < 1000
60 |
61 | // Validate
62 | XCTAssertEqual(logsCount, 10/logMessageData.count + 1)
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/Log4swiftPerformanceTests/PatternFormatterPerformanceTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PatternFormatterPerformanceTests.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 03/09/2015.
6 | // Copyright © 2015 jerome. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import Log4swift
11 |
12 | class PatternFormatterPerformanceTests: XCTestCase {
13 |
14 | func testSimpleFormatterPerformance() {
15 | let formatter = try! PatternFormatter(identifier:"testFormatter", pattern: "[%l][%n][%d] %m")
16 | let info: LogInfoDictionary = [
17 | LogInfoKeys.LoggerName: "nameOfTheLogger",
18 | LogInfoKeys.LogLevel: LogLevel.Info
19 | ]
20 |
21 | self.measure() {
22 | for _ in 1...10000 {
23 | _ = formatter.format(message: "Log message", info: info)
24 | }
25 | }
26 | }
27 |
28 | func testStrftimeDateFormatterPerformance() {
29 | let formatter = try! PatternFormatter(identifier:"testFormatter", pattern: "%d{'format':'%d.%m.%y %k:%M:%s'}")
30 | let info: LogInfoDictionary = [
31 | LogInfoKeys.LoggerName: "nameOfTheLogger",
32 | LogInfoKeys.LogLevel: LogLevel.Info
33 | ]
34 |
35 | self.measure() {
36 | for _ in 1...10000 {
37 | _ = formatter.format(message: "Log message", info: info)
38 | }
39 | }
40 | }
41 |
42 | func testCocoaDateFormatterPerformance() {
43 | let formatter = try! PatternFormatter(identifier:"testFormatter", pattern: "%D{'format':'dd.MM.yyyy HH:mm:ss.SSS'}")
44 | let info: LogInfoDictionary = [
45 | LogInfoKeys.LoggerName: "nameOfTheLogger",
46 | LogInfoKeys.LogLevel: LogLevel.Info
47 | ]
48 |
49 | self.measure() {
50 | for _ in 1...10000 {
51 | _ = formatter.format(message: "Log message", info: info)
52 | }
53 | }
54 | }
55 |
56 | func testComplexFormatPerformance() {
57 | let formatter = try! PatternFormatter(identifier:"testFormatter", pattern: "%D{'format':'yyyy-MM-dd HH:mm:ss.SSS'} [%l{'padding':'-5'}][%n][%f:%L][%M] %m")
58 | let info: LogInfoDictionary = [
59 | LogInfoKeys.LoggerName: "testName",
60 | LogInfoKeys.LogLevel: LogLevel.Info,
61 | LogInfoKeys.FileName: "/Users/test/Swift/test.swift",
62 | LogInfoKeys.FileLine: 42,
63 | LogInfoKeys.Function: "testFunction",
64 | LogInfoKeys.Timestamp: 123456789.876
65 | ]
66 |
67 | self.measure() {
68 | for _ in 1...10000 {
69 | _ = formatter.format(message: "Log message", info: info)
70 | }
71 | }
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/Log4swiftTests/RotationPolicy/DateRotationPolicyTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateRotationPolicyTest.swift
3 | // log4swiftTests
4 | //
5 | // Created by Jérôme Duquennoy on 24/08/2018.
6 | // Copyright © 2018 jerome. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import XCTest
11 | @testable import Log4swift
12 |
13 | class DateRotationPolicyTest: XCTestCase {
14 | func testRotationIsRequestedWhenAgeExceedsLimit() throws {
15 | let filePath = try self.createTemporaryFilePath(fileExtension: "log")
16 | let policy = DateRotationPolicy(maxFileAge: 10)
17 | let fileCreationInterval = -1 * (policy.maxFileAge + 1)
18 | FileManager.default.createFile(atPath: filePath, contents: nil, attributes: [FileAttributeKey.creationDate: Date(timeIntervalSinceNow: fileCreationInterval)])
19 |
20 | policy.appenderDidOpenFile(atPath: filePath)
21 |
22 | // Execute & validate
23 | XCTAssertTrue(policy.shouldRotate())
24 | }
25 |
26 | func testRotationIsNotRequestedWhenFileAgeDoesNotExceedLimit() throws {
27 | let filePath = try self.createTemporaryFilePath(fileExtension: "log")
28 | let policy = DateRotationPolicy(maxFileAge: 10)
29 | let fileCreationInterval = -1 * (policy.maxFileAge - 1)
30 | FileManager.default.createFile(atPath: filePath, contents: nil, attributes: [FileAttributeKey.creationDate: Date(timeIntervalSinceNow: fileCreationInterval)])
31 |
32 | policy.appenderDidOpenFile(atPath: filePath)
33 |
34 | // Execute & validate
35 | XCTAssertFalse(policy.shouldRotate())
36 | }
37 |
38 | func testRotationIsNotRequestedIfNoFileWasOpened() {
39 | let policy = DateRotationPolicy(maxFileAge: 10)
40 |
41 | // Execute & validate
42 | XCTAssertFalse(policy.shouldRotate())
43 | }
44 |
45 | func testRotationIsNotRequestedIfNonExistingFileWasOpened() {
46 | let policy = DateRotationPolicy(maxFileAge: 10)
47 |
48 | // Execute & validate
49 | policy.appenderDidOpenFile(atPath: "/File/that/does/not/exist.log")
50 | XCTAssertFalse(policy.shouldRotate())
51 | }
52 |
53 | func testFileDateDefaultsToNowIfFileDoesNotExist() {
54 | let policy = DateRotationPolicy(maxFileAge: 1)
55 |
56 | // Execute & validate
57 | policy.appenderDidOpenFile(atPath: "/File/that/does/not/exist.log")
58 | XCTAssertFalse(policy.shouldRotate())
59 | Thread.sleep(forTimeInterval: 1.5)
60 | XCTAssertTrue(policy.shouldRotate())
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Log4swiftPerformanceTests/FileAppenderPerformanceTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileAppenderPerformanceTests.swift
3 | // log4swiftPerformanceTests
4 | //
5 | // Created by Jérôme Duquennoy on 07/08/2018.
6 | // Copyright © 2018 jerome. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import Log4swift
11 |
12 | class FileAppenderPerformanceTests: XCTestCase {
13 | var logFilePath: String = ""
14 |
15 | override func setUp() {
16 | XCTAssertNoThrow(
17 | self.logFilePath = try self.createTemporaryFilePath(fileExtension: "log")
18 | )
19 | }
20 |
21 | override func tearDown() {
22 | try! FileManager().removeItem(atPath: self.logFilePath)
23 | }
24 |
25 |
26 | func testFileAppenderPerformanceWhenFileIsNotDeleted() {
27 | do {
28 | let tempFilePath = try self.createTemporaryFilePath(fileExtension: "log")
29 | let fileAppender = FileAppender(identifier: "test.appender", filePath: tempFilePath)
30 | defer {
31 | unlink((tempFilePath as NSString).fileSystemRepresentation)
32 | }
33 |
34 | measure { () -> Void in
35 | for _ in 1...1000 {
36 | fileAppender.log("This is a test log", level: LogLevel.Debug, info: LogInfoDictionary())
37 | }
38 | }
39 | } catch let error {
40 | XCTAssert(false, "Error in test : \(error)")
41 | }
42 | }
43 |
44 | func testPerformanceWithoutRotation() throws {
45 | let appender = FileAppender(identifier: "testAppender", filePath: self.logFilePath)
46 |
47 | self.measure {
48 | for _ in 1...10_000 {
49 | appender.performLog("This is a test log string", level: .Info, info: LogInfoDictionary())
50 | }
51 | }
52 | }
53 |
54 | func testPerformanceWithDateRotationTrigger() throws {
55 | let appender = FileAppender(identifier: "testAppender", filePath: self.logFilePath, maxFileAge: 60*60)
56 |
57 | self.measure {
58 | for _ in 1...10_000 {
59 | appender.performLog("This is a test log string", level: .Info, info: LogInfoDictionary())
60 | }
61 | }
62 | }
63 |
64 | func testPerformanceWithSizeRotationTrigger() throws {
65 | let appender = FileAppender(identifier: "testAppender", filePath: self.logFilePath, maxFileSize: 1024 * 1024)
66 |
67 | self.measure {
68 | for _ in 1...10_000 {
69 | appender.performLog("This is a test log string", level: .Info, info: LogInfoDictionary())
70 | }
71 | }
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/Log4swift/LogLevel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LogLevel.swift
3 | // log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 14/06/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import Foundation
22 |
23 | /**
24 | Log level defines the importance of the log : is it just a debug log, an informational notice, or an error.
25 | Order of the levels is :
26 |
27 | Trace < Debug < Info < Warning < Error < Fatal < Off
28 | */
29 | @objc public enum LogLevel: Int, CustomStringConvertible {
30 |
31 | case Trace = 0
32 | case Debug = 1
33 | case Info = 2
34 | case Warning = 3
35 | case Error = 4
36 | case Fatal = 5
37 | case Off = 6
38 |
39 | /// Converts a string to a log level if possible.
40 | /// This initializer is not case sensitive
41 | public init?(_ stringValue: String) {
42 | switch(stringValue.lowercased()) {
43 | case LogLevel.Trace.description.lowercased():
44 | self = .Trace
45 | case LogLevel.Debug.description.lowercased():
46 | self = .Debug
47 | case LogLevel.Info.description.lowercased():
48 | self = .Info
49 | case LogLevel.Warning.description.lowercased():
50 | self = .Warning
51 | case LogLevel.Error.description.lowercased():
52 | self = .Error
53 | case LogLevel.Fatal.description.lowercased():
54 | self = .Fatal
55 | case LogLevel.Off.description.lowercased():
56 | self = .Off
57 | default:
58 | return nil
59 | }
60 | }
61 |
62 | /// Returns a human readable representation of the log level.
63 | public var description : String {
64 | get {
65 | switch(self) {
66 | case .Trace:
67 | return "Trace"
68 | case .Debug:
69 | return "Debug"
70 | case .Info:
71 | return "Info"
72 | case .Warning:
73 | return "Warning"
74 | case .Error:
75 | return "Error"
76 | case .Fatal:
77 | return "Fatal"
78 | case .Off:
79 | return "Off"
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/Log4swift/Utilities/FileObserver.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileObserver.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 29/12/2015.
6 | // Copyright © 2015 jerome. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | This protocol should be implemented to register as a delegate of a FileObserver object.
13 | */
14 | public protocol FileObserverDelegate {
15 | func fileChanged(atPath: String)
16 | }
17 |
18 | /**
19 | This class is a simple observer for a file. It will notify a delegate when a change is detected.
20 | It does not use FSEvent, to keep the complexity low while being compatible with both OS X and Linux.
21 | It only observes a single file, which will make the cost of pooling for changes very low.
22 | */
23 | public class FileObserver {
24 | public var delegate: FileObserverDelegate?
25 | public let filePath: String
26 | public let poolInterval: Double
27 | private var lastModificationTime = timespec(tv_sec: 0, tv_nsec: 0)
28 |
29 | init(filePath: String, poolInterval: Double = 2.0) {
30 | self.filePath = filePath
31 | self.poolInterval = poolInterval
32 | self.lastModificationTime = self.getFileModificationDate()
33 | self.scheduleNextPooling()
34 | }
35 |
36 | private func getFileModificationDate() -> timespec {
37 | var fileStat = stat()
38 | let statResult = stat(filePath, &fileStat)
39 |
40 | var modificationTimestamp = timespec(tv_sec: 0, tv_nsec: 0)
41 | if statResult == 0 {
42 | modificationTimestamp = fileStat.st_mtimespec
43 | }
44 |
45 | return modificationTimestamp
46 | }
47 |
48 | func poolForChange() {
49 | let modificationDate = self.getFileModificationDate()
50 | if modificationDate > self.lastModificationTime {
51 | delegate?.fileChanged(atPath: self.filePath)
52 | self.lastModificationTime = modificationDate
53 | }
54 | self.scheduleNextPooling()
55 | }
56 |
57 | private func scheduleNextPooling() {
58 | let nextPoolingTime = DispatchTime.now() + self.poolInterval
59 | let poolClosure:@convention(block) () -> Void = {[weak self] in
60 | self?.poolForChange()
61 | }
62 |
63 | if #available(OSX 10.10, *) {
64 | DispatchQueue.main.asyncAfter(deadline: nextPoolingTime, qos: .background, execute: poolClosure)
65 | } else {
66 | DispatchQueue.main.asyncAfter(deadline: nextPoolingTime, execute: poolClosure)
67 | }
68 | }
69 | }
70 |
71 | private func >(left: timespec, right: timespec) -> Bool {
72 | if left.tv_sec > right.tv_sec {
73 | return true
74 | } else if left.tv_sec == right.tv_sec && left.tv_nsec > right.tv_nsec {
75 | return true
76 | }
77 |
78 | return false
79 | }
80 |
--------------------------------------------------------------------------------
/TestProjects/iOS+watchOS-swift-carthage/Log4swiftTestApp WatchKit Extension/ExtensionDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExtensionDelegate.swift
3 | // iOS+watchOS-swift-carthage WatchKit Extension
4 | //
5 | // Created by Jérôme Duquennoy on 04/08/2018.
6 | // Copyright © 2018 fr.duquennoy. All rights reserved.
7 | //
8 |
9 | import WatchKit
10 |
11 | class ExtensionDelegate: NSObject, WKExtensionDelegate {
12 |
13 | func applicationDidFinishLaunching() {
14 | // Perform any final initialization of your application.
15 | }
16 |
17 | func applicationDidBecomeActive() {
18 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
19 | }
20 |
21 | func applicationWillResignActive() {
22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
23 | // Use this method to pause ongoing tasks, disable timers, etc.
24 | }
25 |
26 | func handle(_ backgroundTasks: Set) {
27 | // Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
28 | for task in backgroundTasks {
29 | // Use a switch statement to check the task type
30 | switch task {
31 | case let backgroundTask as WKApplicationRefreshBackgroundTask:
32 | // Be sure to complete the background task once you’re done.
33 | backgroundTask.setTaskCompletedWithSnapshot(false)
34 | case let snapshotTask as WKSnapshotRefreshBackgroundTask:
35 | // Snapshot tasks have a unique completion call, make sure to set your expiration date
36 | snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil)
37 | case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask:
38 | // Be sure to complete the connectivity task once you’re done.
39 | connectivityTask.setTaskCompletedWithSnapshot(false)
40 | case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
41 | // Be sure to complete the URL session task once you’re done.
42 | urlSessionTask.setTaskCompletedWithSnapshot(false)
43 | default:
44 | // make sure to complete unhandled task types
45 | task.setTaskCompletedWithSnapshot(false)
46 | }
47 | }
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/Log4swift/Appenders/SystemAppender.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SystemAppender.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 24/10/2017.
6 | // Copyright © 2017 jerome. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import Foundation
22 |
23 | /**
24 | The SystemAppender is a meta-appender, that will select the preferable appender depending on the system.
25 | - For MacOS 10.11, it will be an ASL appender.
26 | - For MacOS 10.12 and latter, it will be a Unified Logging System appender
27 | - ...
28 | This appender is the best suited one for production software that targets multiple platforms.
29 | */
30 | @objc
31 | public class SystemAppender: Appender {
32 | public override var thresholdLevel: LogLevel {
33 | get {
34 | return self.backendAppender?.thresholdLevel ?? .Off
35 | }
36 | set {
37 | self.backendAppender?.thresholdLevel = newValue
38 | }
39 | }
40 | public override var formatter: Formatter? {
41 | get {
42 | return self.backendAppender?.formatter
43 | }
44 | set {
45 | self.backendAppender?.formatter = newValue
46 | }
47 | }
48 |
49 | internal let backendAppender: Appender?
50 |
51 | @objc
52 | public required init(_ identifier: String) {
53 | if #available(iOS 10.0, macOS 10.12, watchOS 3, *) {
54 | self.backendAppender = AppleUnifiedLoggerAppender(identifier)
55 | } else if #available(iOS 9.0, macOS 10.9, *) {
56 | self.backendAppender = ASLAppender(identifier)
57 | } else {
58 | self.backendAppender = nil
59 | NSLog("No system appender found for current system")
60 | }
61 | super.init(identifier)
62 | }
63 |
64 | internal init(_ identifier: String, withBackendAppender appender: Appender?) {
65 | self.backendAppender = appender
66 |
67 | super.init(identifier)
68 | }
69 |
70 | public override func update(withDictionary dictionary: Dictionary, availableFormatters: Array) throws {
71 | try self.backendAppender?.update(withDictionary: dictionary, availableFormatters: availableFormatters)
72 | }
73 |
74 | public override func performLog(_ log: String, level: LogLevel, info: LogInfoDictionary) {
75 | self.backendAppender?.performLog(log, level: level, info: info)
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Log4swift/Appenders/Appender.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Appender.swift
3 | // log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 14/06/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import Foundation
22 |
23 | /**
24 | Appenders are responsible for sending logs to heir destination.
25 | This class is the base class, from which all appenders should inherit.
26 | */
27 | @objc open class Appender: NSObject {
28 | public enum DictionaryKey: String {
29 | case ThresholdLevel = "ThresholdLevel"
30 | case FormatterId = "FormatterId"
31 | }
32 |
33 | @objc let identifier: String
34 | @objc public var thresholdLevel = LogLevel.Debug
35 | public var formatter: Formatter?
36 |
37 | @objc
38 | public required init(_ identifier: String) {
39 | self.identifier = identifier
40 | }
41 |
42 | open func update(withDictionary dictionary: Dictionary, availableFormatters: Array) throws {
43 | if let safeThresholdString = (dictionary[DictionaryKey.ThresholdLevel.rawValue] as? String) {
44 | if let safeThreshold = LogLevel(safeThresholdString) {
45 | thresholdLevel = safeThreshold
46 | } else {
47 | throw NSError.Log4swiftError(description: "Invalid '\(DictionaryKey.ThresholdLevel.rawValue)' for appender '\(self.identifier)'")
48 | }
49 | }
50 |
51 | if let safeFormatterId = (dictionary[DictionaryKey.FormatterId.rawValue] as? String) {
52 | if let formatter = availableFormatters.find(filter: { $0.identifier == safeFormatterId }) {
53 | self.formatter = formatter
54 | } else {
55 | throw NSError.Log4swiftError(description: "No such formatter '\(safeFormatterId)' for appender \(self.identifier)")
56 | }
57 | }
58 | }
59 |
60 | open func performLog(_ log: String, level: LogLevel, info: LogInfoDictionary) {
61 | // To be overriden by subclasses
62 | }
63 |
64 | final func log(_ log: String, level: LogLevel, info: LogInfoDictionary) {
65 | if(level.rawValue >= self.thresholdLevel.rawValue) {
66 | let logMessage: String
67 |
68 | if let formatter = self.formatter {
69 | logMessage = formatter.format(message: log, info: info)
70 | } else {
71 | logMessage = log
72 | }
73 |
74 | self.performLog(logMessage, level: level, info: info)
75 | }
76 | }
77 | }
78 |
79 | extension Appender {
80 |
81 | static var availableAppenderTypes: [Appender.Type] {
82 | return AppendersRegistry.appenders
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Log4swiftTests/Utilities/FileObserverTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileObserverTests.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 01/01/2016.
6 | // Copyright © 2016 jerome. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Log4swift
11 |
12 | private class FakeObserverDelegate: FileObserverDelegate {
13 | var changes = Array()
14 | let expectation: XCTestExpectation?
15 |
16 | init(expectation: XCTestExpectation? = nil) {
17 | self.expectation = expectation
18 | }
19 |
20 | func fileChanged(atPath filePath: String) {
21 | changes.append(filePath)
22 | expectation?.fulfill()
23 | }
24 | }
25 |
26 | class FileObserverTests: XCTestCase {
27 |
28 | override func setUp() {
29 | super.setUp()
30 | }
31 |
32 | override func tearDown() {
33 | super.tearDown()
34 | }
35 |
36 | func testDelegateIsNotNotifiedIfFileIsNotModified() {
37 | let filePath = try! self.createTemporaryFilePath(fileExtension: "txt")
38 | try! "original test file content".write(toFile: filePath, atomically: false, encoding: String.Encoding.utf8)
39 | // let expectation = expectationWithDescription("File modification notified")
40 | let delegate = FakeObserverDelegate()
41 | let observer = FileObserver(filePath: filePath, poolInterval: 0.1)
42 | observer.delegate = delegate
43 |
44 | // Execute
45 | // man nothing to execute : the test is to validate behavior when nothing happens.
46 |
47 | RunLoop.current.run(until: Date(timeIntervalSinceNow: 1.0))
48 |
49 | // Validate
50 | XCTAssertEqual(delegate.changes.count, 0, "Delegate should have received no modification notification")
51 | }
52 |
53 | func testDelegateIsNotifiedWhenFileChanges() {
54 | let filePath = try! self.createTemporaryFilePath(fileExtension: "txt")
55 | try! "original test file content".write(toFile: filePath, atomically: false, encoding: String.Encoding.utf8)
56 | let expectation = self.expectation(description: "File modification notified")
57 | let delegate = FakeObserverDelegate(expectation: expectation)
58 | let observer = FileObserver(filePath: filePath, poolInterval: 0.1)
59 | observer.delegate = delegate
60 |
61 | sleep(1); // the modification date resolution for file is 1 second, we should not be faster.
62 |
63 | // Execute
64 | try! "modified test file content".write(toFile: filePath, atomically: false, encoding: String.Encoding.utf8)
65 |
66 | waitForExpectations(timeout: Double(observer.poolInterval * 2.0), handler: nil)
67 |
68 | // Validate
69 | XCTAssertEqual(delegate.changes.count, 1, "Delegate should have received one modification notification")
70 | }
71 |
72 | func testDelegateIsNotifiedWhenNonExistingFileIsCreated() {
73 | let filePath = try! self.createTemporaryFilePath(fileExtension: "txt")
74 | let expectation = self.expectation(description: "File modification notified")
75 | let delegate = FakeObserverDelegate(expectation: expectation)
76 | let observer = FileObserver(filePath: filePath, poolInterval: 0.1)
77 | observer.delegate = delegate
78 |
79 | // Execute
80 | try! "modified test file content".write(toFile: filePath, atomically: false, encoding: String.Encoding.utf8)
81 |
82 | waitForExpectations(timeout: Double(observer.poolInterval * 2.0), handler: nil)
83 |
84 | // Validate
85 | XCTAssertEqual(delegate.changes.count, 1, "Delegate should have received one modification notification")
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Log4swift/Utilities/String+utilities.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+utilities.swift
3 | // log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 14/06/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | extension StringProtocol {
22 | /// Return a new string by removing everything after the last occurence of the provided marker and including the marker.
23 | /// If the marker is not found, an empty string is returned.
24 | public func stringByRemovingLastComponent(withDelimiter delimiter: String) -> SubSequence? {
25 | guard let markerIndex = self.reversed().firstIndex(of: Character(delimiter)) else { return nil }
26 | let endIndex = self.index(markerIndex.base, offsetBy: -1)
27 |
28 | let result = self[self.startIndex.. String {
36 | guard args.count > 0 else {
37 | return self
38 | }
39 |
40 | return withVaList(args) { (argsListPointer) in
41 | NSString(format: self, arguments: argsListPointer) as String
42 | }
43 | }
44 |
45 | /// Pads string left or right to a certain width.
46 | ///
47 | /// :parameter: width: The width of the final string. Positive values left-justify the value,
48 | /// negative values right-justify it. Default value is `0` and causes no
49 | /// padding to occur. If the string is longer than the specified width,
50 | /// it will be truncated.
51 | ///
52 | /// :returns: The padded string
53 | public func pad(toWidth width: Int) -> String {
54 | // var str = self as NSString
55 | var paddedString: String = self
56 |
57 | if width == 0 {
58 | return self
59 | }
60 |
61 | if self.count > abs(width) {
62 | if width < 0 {
63 | paddedString = String(self.suffix(abs(width)))
64 | } else {
65 | paddedString = String(self.prefix(width))
66 | }
67 | }
68 |
69 | if self.count < abs(width) {
70 | if width < 0 {
71 | paddedString = " ".padding(toLength: abs(width) - self.count, withPad: " ", startingAt: 0) + self
72 | } else {
73 | paddedString = self.padding(toLength: width, withPad: " ", startingAt: 0)
74 | }
75 | }
76 |
77 | return paddedString
78 | }
79 |
80 |
81 | /// Returns a dictionary if String contains proper JSON format for a single, non-nested object; a simple dictionary.
82 | /// Keys and values should be surrounded with single or double quotes.
83 | /// Ex: {"name":"value", 'name':'value'}
84 | public func toDictionary() throws -> [String:AnyObject] {
85 | var dict: [String:AnyObject] = Dictionary()
86 | let s = (self as NSString).replacingOccurrences(of: "'", with: "\"")
87 |
88 | if let data = s.data(using: String.Encoding.utf8) {
89 | do {
90 | dict = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as! [String:AnyObject]
91 | }
92 | }
93 |
94 | return dict
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/Log4swiftTests/Appenders/SystemAppenderTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SystemAppenderTests.swift
3 | // log4swiftTests
4 | //
5 | // Created by Jérôme Duquennoy on 24/10/2017.
6 | // Copyright © 2017 jerome. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Log4swift
11 |
12 | class TestAppender: Appender {
13 | var didPerformLog = false
14 | override func performLog(_ log: String, level: LogLevel, info: LogInfoDictionary) {
15 | self.didPerformLog = true
16 | }
17 | }
18 |
19 | class SystemAppenderTests: XCTestCase {
20 |
21 | func testBackendFormatterIsNotNil() {
22 | // Execute
23 | let appender = SystemAppender("testAppender")
24 |
25 | XCTAssertNotNil(appender.backendAppender)
26 | }
27 |
28 | func testSetFormatterIsForwardedToBackendAppender() {
29 | let appender = SystemAppender("testAppender")
30 | let formatter = PatternFormatter("appender")
31 |
32 | // Execute
33 | appender.formatter = formatter
34 |
35 | if let backendFormatter = appender.backendAppender?.formatter as? PatternFormatter {
36 | XCTAssert(backendFormatter === formatter)
37 | } else {
38 | XCTFail("Formatter was not forwarded to backend appender")
39 | }
40 | }
41 |
42 | func testGetFormatterIsForwardedToBackendAppender() {
43 | let appender = SystemAppender("testAppender")
44 | let formatter = PatternFormatter("appender")
45 |
46 | appender.backendAppender?.formatter = formatter
47 |
48 | // Execute
49 | let readFormatter = appender.formatter
50 |
51 | if let readFormatter = readFormatter as? PatternFormatter {
52 | XCTAssert(readFormatter === formatter)
53 | } else {
54 | XCTFail("Formatter was not forwarded to backend appender")
55 | }
56 | }
57 |
58 | func testSetThresholdIsForwardedToBackendAppender() {
59 | let appender = SystemAppender("testAppender")
60 |
61 | // Execute
62 | appender.thresholdLevel = .Error
63 |
64 | if let backendThreshold = appender.backendAppender?.thresholdLevel {
65 | XCTAssertEqual(backendThreshold, .Error)
66 | } else {
67 | XCTFail("No threshold level found for backend appender")
68 | }
69 | }
70 |
71 | func testGetThresholdIsForwardedToBackendAppender() {
72 | let appender = SystemAppender("testAppender")
73 |
74 | appender.backendAppender?.thresholdLevel = .Error
75 |
76 | // execute
77 | let readThresholdLevel = appender.thresholdLevel
78 |
79 | XCTAssertEqual(readThresholdLevel, .Error)
80 | }
81 |
82 | func testGetThresholdReturnsNilIfBackendAppenderIsNil() {
83 | let appender = SystemAppender("testAppender", withBackendAppender: nil)
84 |
85 | // execute
86 | let readThresholdLevel = appender.thresholdLevel
87 |
88 | XCTAssertEqual(readThresholdLevel, .Off)
89 | }
90 |
91 | func testUpdateWithDictionaryIsForwardedToBackendAppender() throws {
92 | let appender = SystemAppender("testAppender")
93 | let updateDictionary = [Appender.DictionaryKey.ThresholdLevel.rawValue: String(describing: LogLevel.Error)]
94 |
95 | // Execute
96 | try appender.update(withDictionary: updateDictionary, availableFormatters: [])
97 |
98 | let readThresholdLevel = appender.thresholdLevel
99 | XCTAssertEqual(readThresholdLevel, .Error)
100 | }
101 |
102 | func testPerformLogIsForwardedToBackendAppender() {
103 | let backendAppender = TestAppender("backend")
104 | let appender = SystemAppender("testAppender", withBackendAppender: backendAppender)
105 |
106 | // Execute
107 | appender.performLog("test log", level: .Error, info: LogInfoDictionary())
108 |
109 | XCTAssertTrue(backendAppender.didPerformLog)
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Log4swift.xcodeproj/xcshareddata/xcschemes/log4swift-OSX.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
38 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
64 |
70 |
71 |
72 |
73 |
79 |
80 |
86 |
87 |
88 |
89 |
91 |
92 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/Log4swift.xcodeproj/xcshareddata/xcschemes/log4swiftPerformanceTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
42 |
48 |
49 |
50 |
51 |
52 |
62 |
63 |
69 |
70 |
71 |
72 |
78 |
79 |
85 |
86 |
87 |
88 |
90 |
91 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/Log4swiftPerformanceTests/PerformanceTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PerformanceTests.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 15/07/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import XCTest
22 | import Log4swift
23 |
24 | class PerformanceTests: XCTestCase {
25 |
26 | func testNSLogPerformanceTest() {
27 | // This is an example of a performance test case.
28 | self.measure() {
29 | for _ in 0...5000 {
30 | NSLog("This is a simple log")
31 | }
32 | }
33 | }
34 |
35 | func testConsoleLoggerWithFormatterPerformanceTest() {
36 | let formatter = try! PatternFormatter(identifier: "formatter", pattern: "%d{'format':'%D %R'} %m")
37 | let stdOutAppender = StdOutAppender("appender")
38 | stdOutAppender.errorThresholdLevel = .Debug
39 | stdOutAppender.formatter = formatter
40 | let logger = Logger(identifier: "")
41 | logger.appenders = [stdOutAppender]
42 |
43 | // This is an example of a performance test case.
44 | self.measure() {
45 | for _ in 0...5000 {
46 | logger.error("This is a simple log")
47 | }
48 | }
49 | }
50 |
51 | func testAsyncConsoleLoggerWithFormatterPerformanceTest() {
52 | let formatter = try! PatternFormatter(identifier: "formatter", pattern: "%d{'format':'%D %R'} %m")
53 | let stdOutAppender = StdOutAppender("appender")
54 | stdOutAppender.errorThresholdLevel = .Debug
55 | stdOutAppender.formatter = formatter
56 | let logger = Logger(identifier: "")
57 | logger.appenders = [stdOutAppender]
58 | logger.asynchronous = true
59 |
60 | // This is an example of a performance test case.
61 | self.measure() {
62 | for _ in 0...5000 {
63 | logger.error("This is a simple log")
64 | }
65 | }
66 | }
67 |
68 | func testFileLoggerWithFormatterPerformanceTest() {
69 | let formatter = try! PatternFormatter(identifier: "formatter", pattern: "%d %m")
70 | let tempFilePath = try! self.createTemporaryFilePath(fileExtension: "log")
71 | let fileAppender = FileAppender(identifier: "test.appender", filePath: tempFilePath)
72 | fileAppender.formatter = formatter
73 | let logger = Logger(identifier: "")
74 | logger.appenders = [fileAppender]
75 |
76 | // This is an example of a performance test case.
77 | self.measure() {
78 | for _ in 0...5000 {
79 | logger.error("This is a simple log")
80 | }
81 | }
82 |
83 | unlink((tempFilePath as NSString).fileSystemRepresentation)
84 | }
85 |
86 | func testASLLoggerWithFormatterPerformanceTest() {
87 | let aslAppender = ASLAppender("appender")
88 | let logger = Logger(identifier: "")
89 | logger.appenders = [aslAppender]
90 |
91 | // This is an example of a performance test case.
92 | self.measure() {
93 | for _ in 0...5000 {
94 | logger.error("This is a perf test log")
95 | }
96 | }
97 | }
98 |
99 | // MARK: LoggerFactory performances
100 |
101 | func testGetLoggerForSamedIdentifierPerformance() {
102 | let factory = LoggerFactory()
103 | for index in 1...10 {
104 | try! factory.registerLogger(Logger(identifier: "test.identifier.\(index)", level: .Info, appenders: [StdOutAppender("test.appender")]))
105 | }
106 |
107 | self.measure() {
108 | for _ in 1...10000 {
109 | _ = factory.getLogger("test.identifier")
110 | }
111 | }
112 | }
113 |
114 | func testGetLoggerForDifferentIdentifierPerformance() {
115 | let factory = LoggerFactory()
116 | for index in 1...10 {
117 | try! factory.registerLogger(Logger(identifier: "test.identifier.\(index * 100)", level: .Info, appenders: [StdOutAppender("test.appender")]))
118 | }
119 |
120 | self.measure() {
121 | for index in 1...10000 {
122 | _ = factory.getLogger("test.identifier.\(index)")
123 | }
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Log4swift changelog
2 |
3 | ## 1.2.0
4 | Addeed an injectable timeProvider on loggers, to improve testability of code using Log4swift (Thanks Sergei)
5 |
6 | ## 1.1.0
7 | Long due version !
8 | - Removed warnings for swift 5
9 | - Added rotation policy for log files, either based on date or file size
10 | - fixed a crash when compiling on some versions of Xcode (objc_copyClassList), and some randomly failing tests
11 |
12 | ## 1.0.4
13 | - Migrated to Swift 4 and xcode 9
14 | - Appender.update(withDictionary:, availableFormatters:) made open, to allow configuration from file of custom appender (Thanks Yurii)
15 | - Added 'p' maker for pattern formatter, that prints the ID of the current process in hexadecimal (Thanks Yurii)
16 |
17 | ## 1.0.3
18 | - Fixed compilation problem with Carthage
19 | - Added AppleUnifiedLoggerAppender
20 | - Added SystemAppender, that uses the best system-provided appender for the host system.
21 |
22 | ## 1.0.2
23 |
24 | - Added watchOS compatibility (Thanks Igor)
25 | - Compiles with swift 3.1 (Thanks Guillem)
26 |
27 | ## 1.0.1
28 |
29 | - Compiles with Xcode 8.1
30 | - PerformLog method accessor changed to open to enable subclassing of Appenders
31 |
32 | ## 1.0
33 |
34 | This is the first version to drop the "beta" flag. After using it in different projects for a while, with no problem, it seems safe to advertise it as non beta now.
35 |
36 | ### Bug fixes
37 | - Fixed a problem causing the FileAppender to erase log file on all new sessions (Thanks to josealobato for this fix)
38 |
39 | ### Pattern formatter enhancements (thanks to Darkdah for those improvements)
40 | - Added a new marker to the pattern formatter:
41 | - %D: the date of the log using NSDateFormatter format (defined by [Unicode Technical Standard #35](http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns)). This notably allows logging of miliseconds
42 |
43 | ## 1.0b5 (2016-03-22)
44 |
45 | ### General changes
46 | - Code base updated to remove use of features deprecated as of swift 2.2
47 |
48 | ### Appenders enhancements
49 | - TTY type for coloration can be forced if auto-detection does not work. This can be useful when debugging a module that will be loaded by another application (such as sytem extensions).
50 |
51 | ### Pattern formatter enhancements (thanks to Darkdah for those improvements)
52 |
53 | - Added two markers to the pattern formatter :
54 | - %f: displays the name of the file where the log was issued (%F displays the full path)
55 | - %M: the name of the method in which the log message was sent
56 |
57 | ### Log configuration
58 |
59 | - Added log levels (thanks to Darkdah for those)
60 | - Off log level added. No messages can be logged with that level, it can only be used as a threshold level in the configuration, to mute a logger or an appender
61 | - Trace level added bellow debug
62 | - Added possibility to automatically reload configuration file when modified
63 |
64 | ## 1.0b4 (2015-11-03)
65 |
66 | ### Loggers enhancements
67 | - Loggers can log asynchronously. This new behavior is opt-in, using the configuration key *Asynchronous* in a configuration dictionary or the property *asynchronous* in code
68 |
69 | ### Pattern formatter enhancements (thanks to RegalMedia for those improvements)
70 | - Markers now receives json-formatted options (**This can break your existing configuration**)
71 | - New padding option is added to all markers
72 |
73 | ### Misc Enhancements
74 | - When configuring loggers with a dictionary (or a file), appenders class name are no longer case sensitive.
75 | - Errors are reported with description in Objective-C. The use of a custom error type was causing all helpful informations to be lost when catching them in the objective-c world (as of swift 2.1, this has been reported to Apple as rdar://23287003)
76 | - Some convenience one-line configuration method are added to LoggerFactory (*configureFor...* methods). This is available in swift only, because of the use of default values for parameters.
77 |
78 | ## 1.0b3 (2015-10-01)
79 |
80 | ### Misc Enhancements
81 | - Origin file and line number markers added to the PatternFormatter. When logging from Objective-C, this requires the use of specific methods with file and line arguments.
82 | - StdOutAppender can now colorize both the text and its background, for Xcode with the Xcodecolors (https://github.com/robbiehanson/XcodeColors) and XTerm-color. Colors can be set using configuration dictionary, and in swift code. It is not possible in objective-c code, due to the use of unsupported features (advanced enums).
83 | - PatternFormatter now uses returns unmodified messages when initialized without a pattern.
84 |
85 | ## 1.0b2 (2015-08-15)
86 | - First version
87 |
--------------------------------------------------------------------------------
/Log4swiftTests/Utilities/String+utilitiesTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+utilitiesTest.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 06/10/2015.
6 | // Copyright © 2015 jerome. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class String_utilitiesTest: XCTestCase {
12 |
13 | func testRemovingLastComponentWitDelimiterRemovesLastComponent() {
14 | let exempleString = "This is a string"
15 |
16 | // Execute
17 | let truncatedString = exempleString.stringByRemovingLastComponent(withDelimiter: " ")
18 |
19 | // Validate
20 | XCTAssertEqual(truncatedString, "This is a")
21 | }
22 |
23 | func testRemovingLastComponentWitDelimiterReturnsNilIfDelimiterIsNotFound() {
24 | let exempleString = "This is a string"
25 |
26 | // Execute
27 | let truncatedString = exempleString.stringByRemovingLastComponent(withDelimiter: ",")
28 |
29 | // Validate
30 | XCTAssertNil(truncatedString)
31 | }
32 |
33 | func testPadToWidthTruncatesEndOfStringIfWidthIsSmallerThanStringLength() {
34 | let exempleString = "1234567890"
35 |
36 | // Execute
37 | let truncatedString = exempleString.pad(toWidth: 5)
38 |
39 | // Validate
40 | XCTAssertEqual(truncatedString, "12345")
41 | }
42 |
43 | func testPadToWidthTruncatesBeginingOfStringIfWidthIsSmallerThanStringLengthAndNegative() {
44 | let exempleString = "1234567890"
45 |
46 | // Execute
47 | let truncatedString = exempleString.pad(toWidth: -5)
48 |
49 | // Validate
50 | XCTAssertEqual(truncatedString, "67890")
51 | }
52 |
53 | func testPadToWidthFillsWithTrailingSpacesIfWidthIsBiggerThanStringLength() {
54 | let exempleString = "1234567890"
55 |
56 | // Execute
57 | let truncatedString = exempleString.pad(toWidth: 12)
58 |
59 | // Validate
60 | XCTAssertEqual(truncatedString, "1234567890 ")
61 | }
62 |
63 | func testPadToWidthFillsWithLeadingSpacesIfWidthIsBiggerThanStringLengthAndNegative() {
64 | let exempleString = "1234567890"
65 |
66 | // Execute
67 | let truncatedString = exempleString.pad(toWidth: -12)
68 |
69 | // Validate
70 | XCTAssertEqual(truncatedString, " 1234567890")
71 | }
72 |
73 | func testPadWithZeroWidthReturnsOriginalString() {
74 | let exempleString = "1234567890"
75 |
76 | // Execute
77 | let truncatedString = exempleString.pad(toWidth: 0)
78 |
79 | // Validate
80 | XCTAssertEqual(truncatedString, "1234567890")
81 | }
82 |
83 | func testToDictionaryWithValidPatterns() {
84 | var dict: [String:AnyObject]
85 |
86 | // Execute
87 | dict = try! "{\"padding\":\"-57\", \"case\": \"upper\"}".toDictionary()
88 |
89 | // Validate
90 | XCTAssertEqual(dict.keys.count, 2)
91 | XCTAssertEqual(dict["padding"] as! String?, "-57")
92 | XCTAssertEqual(dict["case"] as! String?, "upper")
93 | XCTAssertEqual(dict["missing"] as! String?, nil)
94 |
95 |
96 | // Execute
97 | dict = try! "{'padding':'-57', 'case': 'upper'}".toDictionary()
98 |
99 | // Validate
100 | XCTAssertEqual(dict.keys.count, 2)
101 | XCTAssertEqual(dict["padding"] as! String?, "-57")
102 | XCTAssertEqual(dict["case"] as! String?, "upper")
103 | XCTAssertEqual(dict["missing"] as! String?, nil)
104 |
105 |
106 | // Execute
107 | dict = try! "{\"padding\":'-57', 'case': \"upper\"}".toDictionary()
108 |
109 | // Validate
110 | XCTAssertEqual(dict.keys.count, 2)
111 | XCTAssertEqual(dict["padding"] as! String?, "-57")
112 | XCTAssertEqual(dict["case"] as! String?, "upper")
113 | XCTAssertEqual(dict["missing"] as! String?, nil)
114 | }
115 |
116 | func testToDictionaryWithInvalidPatterns() {
117 | var dict: [String:AnyObject]? = nil
118 |
119 | // Execute/Validate
120 | XCTAssertThrows { try dict = "{\"padding\":-57, case: \"upper\"}".toDictionary() }
121 | XCTAssertThrows { try dict = "\"padding\":\"-57\", \"case\": \"upper\"".toDictionary() }
122 | XCTAssertNil(dict)
123 | }
124 |
125 | func testFormatLongStringWithPercentCharsButNoArguments() {
126 | let appender = MemoryAppender()
127 | appender.thresholdLevel = .Debug
128 | let pattern = "This is a string \"with\" special %characters including escapes : %x %2C %d %s %2C %s %2C"
129 | var logString = ""
130 |
131 | for index in 0...10000 {
132 | logString += "pattern #\(index): \(pattern)"
133 | }
134 |
135 | // Execute
136 | _ = logString.format(args: [])
137 |
138 | // Validate
139 | // Nothing to do, it should just not crash
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/Log4swift.xcodeproj/xcshareddata/xcschemes/log4swift-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
45 |
46 |
52 |
53 |
54 |
55 |
57 |
63 |
64 |
65 |
66 |
67 |
77 |
78 |
84 |
85 |
86 |
87 |
93 |
94 |
100 |
101 |
102 |
103 |
105 |
106 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/Log4swiftTests/Appenders/ASLAppenderTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ASLAppenderTests.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 31/07/15.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import XCTest
22 | @testable import Log4swift
23 |
24 | class ASLAppenderTests: XCTestCase {
25 | func testASLAppenderLogsFatalMessagesWithErrorLevel() {
26 | let appender = ASLAppender("testAppender")
27 | let logMessage = "Test fatal message " + UUID().uuidString
28 |
29 | // Execute
30 | appender.log(logMessage, level: LogLevel.Fatal, info: LogInfoDictionary())
31 |
32 | sleep(1)
33 |
34 | // Validate
35 | let levelOfMessageInAsl = appender.aslClient.getLevelOfMessage(matchingText: logMessage)
36 | XCTAssertEqual(levelOfMessageInAsl, Int32(LogLevel.Fatal.rawValue))
37 | }
38 |
39 | func testASLAppenderLogsErrorMessagesWithErrorLevel() {
40 | let appender = ASLAppender("testAppender")
41 | let logMessage = "Test error message " + UUID().uuidString
42 |
43 | // Execute
44 | appender.log(logMessage, level: LogLevel.Error, info: LogInfoDictionary())
45 |
46 | // Validate
47 | let levelOfMessageInAsl = appender.aslClient.getLevelOfMessage(matchingText: logMessage)
48 | XCTAssertEqual(levelOfMessageInAsl, Int32(LogLevel.Error.rawValue))
49 | }
50 |
51 | func testASLAppenderLogsWarningMessagesWithWarningLevel() {
52 | let appender = ASLAppender("testAppender")
53 | let logMessage = "Test warning message " + UUID().uuidString
54 |
55 | // Execute
56 | appender.log(logMessage, level: LogLevel.Warning, info: LogInfoDictionary())
57 |
58 | // Validate
59 | let levelOfMessageInAsl = appender.aslClient.getLevelOfMessage(matchingText: logMessage)
60 | XCTAssertEqual(levelOfMessageInAsl, Int32(LogLevel.Warning.rawValue))
61 | }
62 |
63 | func testASLAppenderLogsWarningMessagesWithInfoLevel() {
64 | let appender = ASLAppender("testAppender")
65 | let logMessage = "Test info message " + UUID().uuidString
66 |
67 | // Execute
68 | appender.log(logMessage, level: LogLevel.Info, info: LogInfoDictionary())
69 |
70 | // Validate
71 | let levelOfMessageInAsl = appender.aslClient.getLevelOfMessage(matchingText: logMessage)
72 | XCTAssertEqual(levelOfMessageInAsl, Int32(LogLevel.Info.rawValue))
73 | }
74 |
75 | func testASLAppenderLogsWarningMessagesWithDebugLevel() {
76 | let appender = ASLAppender("testAppender")
77 | let logMessage = "Test debug message " + UUID().uuidString
78 |
79 | // Execute
80 | appender.log(logMessage, level: LogLevel.Debug, info: LogInfoDictionary())
81 |
82 | // Validate
83 | let levelOfMessageInAsl = appender.aslClient.getLevelOfMessage(matchingText: logMessage)
84 | XCTAssertEqual(levelOfMessageInAsl, Int32(LogLevel.Debug.rawValue))
85 | }
86 |
87 | func testASLAppenderUsesLoggerNameAsCategoryIfProvided() {
88 | let appender = ASLAppender("testAppender")
89 | let logMessage = "Test message with facility " + UUID().uuidString
90 | let info: LogInfoDictionary = [LogInfoKeys.LoggerName: "That is a nice logger name"]
91 |
92 | // Execute
93 | appender.log(logMessage, level: LogLevel.Debug, info: info)
94 |
95 | // Validate
96 | let messageFacility = appender.aslClient.getFacilityOfMessage(matchingText: logMessage)
97 | if let messageFacility = messageFacility {
98 | XCTAssertEqual(messageFacility, info[LogInfoKeys.LoggerName]!.description)
99 | } else {
100 | XCTFail("Message not logged")
101 | }
102 | }
103 |
104 | func testASLAppenderLogMessagesWithoutTryingToInterpretFormatMarkers() {
105 | let appender = ASLAppender("testAppender")
106 | let logMessage = "Test message with uninterpretted formatting markers : %f (id=" + UUID().uuidString + ")"
107 | let info: LogInfoDictionary = [LogInfoKeys.LoggerName: "That is a nice logger name"]
108 |
109 | // Execute
110 | appender.log(logMessage, level: LogLevel.Error, info: info)
111 |
112 | // Validate
113 | let messageFacility = appender.aslClient.getFacilityOfMessage(matchingText: logMessage)
114 | XCTAssertTrue(messageFacility != nil, "Logged message not found in ASL")
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/Log4swift/Objective-c wrappers/ASLWrapper.m:
--------------------------------------------------------------------------------
1 | //
2 | // ASLWrapper.m
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 29/07/15.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import "ASLWrapper.h"
22 | #import
23 | #import
24 |
25 | @implementation ASLWrapper {
26 | aslclient logClient;
27 | dispatch_queue_t loggingQueue;
28 | }
29 |
30 | - (instancetype)init {
31 | self = [super init];
32 | if (self) {
33 | logClient = asl_open(NULL, NULL, 0);
34 | char filter = (char) ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG);
35 | asl_set_filter(logClient, filter); // We don't want ASL to filter messages
36 | loggingQueue = dispatch_queue_create("Log4swift.ASLLoggingQueue", DISPATCH_QUEUE_SERIAL);
37 | }
38 | return self;
39 | }
40 |
41 | - (void)dealloc {
42 | if(logClient != NULL) {
43 | asl_close(logClient);
44 | logClient = NULL;
45 |
46 | loggingQueue = NULL;
47 | }
48 | }
49 |
50 | - (void)logMessage:(NSString *)log level:(int)level category:(NSString *)category {
51 | static char const *const levelStrings[] = {"0", "1", "2", "3", "4", "5", "6", "7"};
52 | dispatch_sync(loggingQueue, ^{
53 | if(self->logClient != NULL) {
54 | int aslLogLevel = [self _logLevelToAslLevel:level];
55 | aslmsg aslMessage = asl_new(ASL_TYPE_MSG);
56 | asl_set(aslMessage, ASL_KEY_FACILITY, [category UTF8String]);
57 | asl_set(aslMessage, ASL_KEY_LEVEL, levelStrings[aslLogLevel]);
58 | asl_set(aslMessage, ASL_KEY_MSG, [log UTF8String]);
59 | asl_send(self->logClient, aslMessage);
60 | asl_free(aslMessage);
61 | }
62 |
63 | });
64 | }
65 |
66 | - (int)getLevelOfMessageMatchingText:(NSString *)message {
67 | aslmsg query = asl_new(ASL_TYPE_QUERY);
68 | asl_set_query(query, ASL_KEY_MSG, [message UTF8String], ASL_QUERY_OP_EQUAL);
69 | aslresponse response = asl_search(logClient, query);
70 | asl_free(query);
71 |
72 | int foundLevel = -1;
73 | aslmsg foundMessage = asl_next(response);
74 | if (foundMessage != NULL) {
75 | const char *level = asl_get(foundMessage, ASL_KEY_LEVEL);
76 | if (level != NULL) {
77 | foundLevel = [[NSString stringWithCString:level encoding:NSUTF8StringEncoding] intValue];
78 | }
79 | }
80 |
81 | asl_release(response);
82 |
83 | return [self _aslLevelToLogLevel:foundLevel];
84 | }
85 |
86 | - (NSString *)getFacilityOfMessageMatchingText:(NSString *)message {
87 | aslmsg query = asl_new(ASL_TYPE_QUERY);
88 | asl_set_query(query, ASL_KEY_MSG, [message UTF8String], ASL_QUERY_OP_EQUAL);
89 | aslresponse response = asl_search(logClient, query);
90 | asl_free(query);
91 |
92 | NSString *foundFacility = nil;
93 | aslmsg foundMessage = asl_next(response);
94 | if (foundMessage != NULL) {
95 | const char *level = asl_get(foundMessage, ASL_KEY_FACILITY);
96 | if (level != NULL) {
97 | foundFacility = [NSString stringWithCString:level encoding:NSUTF8StringEncoding];
98 | }
99 | }
100 |
101 | asl_release(response);
102 |
103 | return foundFacility;
104 | }
105 |
106 | - (int)_logLevelToAslLevel:(LogLevel)logLevel {
107 | int aslLogLevel = ASL_LEVEL_DEBUG;
108 | switch(logLevel) {
109 | case LogLevelTrace:
110 | case LogLevelDebug:
111 | aslLogLevel = ASL_LEVEL_DEBUG;
112 | break;
113 | case LogLevelInfo:
114 | aslLogLevel = ASL_LEVEL_INFO;
115 | break;
116 | case LogLevelWarning:
117 | aslLogLevel = ASL_LEVEL_WARNING;
118 | break;
119 | case LogLevelError:
120 | aslLogLevel = ASL_LEVEL_ERR;
121 | break;
122 | case LogLevelFatal:
123 | aslLogLevel = ASL_LEVEL_CRIT;
124 | break;
125 | case LogLevelOff:
126 | // If the LogLevel is OFF this piece of code should have never been reached in the first place
127 | // Mapping it to ASL_LEVEL_CRIT if does nevertheless.
128 | aslLogLevel = ASL_LEVEL_CRIT;
129 | break;
130 | }
131 | return aslLogLevel;
132 | }
133 |
134 | - (int)_aslLevelToLogLevel:(int)aslLevel {
135 | int aslLogLevel = ASL_LEVEL_DEBUG;
136 | switch(aslLevel) {
137 | case ASL_LEVEL_DEBUG:
138 | aslLogLevel = LogLevelDebug;
139 | break;
140 | case ASL_LEVEL_INFO:
141 | case ASL_LEVEL_NOTICE:
142 | aslLogLevel = LogLevelInfo;
143 | break;
144 | case ASL_LEVEL_WARNING:
145 | aslLogLevel = LogLevelWarning;
146 | break;
147 | case ASL_LEVEL_ERR:
148 | aslLogLevel = LogLevelError;
149 | break;
150 | case ASL_LEVEL_CRIT:
151 | case ASL_LEVEL_ALERT:
152 | case ASL_LEVEL_EMERG:
153 | aslLogLevel = LogLevelFatal;
154 | break;
155 | }
156 | return aslLogLevel;
157 | }
158 | @end
159 |
--------------------------------------------------------------------------------
/Third parties/NSLogger/NSLogger.h:
--------------------------------------------------------------------------------
1 | /*
2 | * NSLogger.h
3 | *
4 | * version 1.5-RC2 22-NOV-2013
5 | *
6 | * Part of NSLogger (client side)
7 | * https://github.com/fpillet/NSLogger
8 | *
9 | * BSD license follows (http://www.opensource.org/licenses/bsd-license.php)
10 | *
11 | * Copyright (c) 2010-2013 Florent Pillet All Rights Reserved.
12 | *
13 | * Redistribution and use in source and binary forms, with or without modification,
14 | * are permitted provided that the following conditions are met:
15 | *
16 | * Redistributions of source code must retain the above copyright notice,
17 | * this list of conditions and the following disclaimer. Redistributions in
18 | * binary form must reproduce the above copyright notice, this list of
19 | * conditions and the following disclaimer in the documentation and/or other
20 | * materials provided with the distribution. Neither the name of Florent
21 | * Pillet nor the names of its contributors may be used to endorse or promote
22 | * products derived from this software without specific prior written
23 | * permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
24 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
25 | * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 | *
35 | */
36 | #import "LoggerClient.h"
37 |
38 |
39 |
40 | // Log level usual usage:
41 | // Level 0: errors only!
42 | // Level 1: important informations, app states…
43 | // Level 2: less important logs, network requests…
44 | // Level 3: network responses, datas and images…
45 | // Level 4: really not important stuff.
46 |
47 |
48 |
49 | #ifdef DEBUG
50 | #define NSLog(...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"NSLog", 0, __VA_ARGS__)
51 | #define LoggerError(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Error", level, __VA_ARGS__)
52 | #define LoggerApp(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"App", level, __VA_ARGS__)
53 | #define LoggerView(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"View", level, __VA_ARGS__)
54 | #define LoggerService(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Service", level, __VA_ARGS__)
55 | #define LoggerModel(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Model", level, __VA_ARGS__)
56 | #define LoggerData(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Data", level, __VA_ARGS__)
57 | #define LoggerNetwork(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Network", level, __VA_ARGS__)
58 | #define LoggerLocation(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Location", level, __VA_ARGS__)
59 | #define LoggerPush(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Push", level, __VA_ARGS__)
60 | #define LoggerFile(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"File", level, __VA_ARGS__)
61 | #define LoggerSharing(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Sharing", level, __VA_ARGS__)
62 | #define LoggerAd(level, ...) LogMessageF(__FILE__, __LINE__, __FUNCTION__, @"Ad and Stat", level, __VA_ARGS__)
63 |
64 | #else
65 | #define NSLog(...) LogMessageCompat(__VA_ARGS__)
66 | #define LoggerError(...) while(0) {}
67 | #define LoggerApp(level, ...) while(0) {}
68 | #define LoggerView(...) while(0) {}
69 | #define LoggerService(...) while(0) {}
70 | #define LoggerModel(...) while(0) {}
71 | #define LoggerData(...) while(0) {}
72 | #define LoggerNetwork(...) while(0) {}
73 | #define LoggerLocation(...) while(0) {}
74 | #define LoggerPush(...) while(0) {}
75 | #define LoggerFile(...) while(0) {}
76 | #define LoggerSharing(...) while(0) {}
77 | #define LoggerAd(...) while(0) {}
78 |
79 | #endif
80 |
81 |
82 |
83 | /// Stringification, see this:
84 | /// http://gcc.gnu.org/onlinedocs/cpp/Stringification.html
85 | #define nslogger_xstr(s) nslogger_str(s)
86 | #define nslogger_str(s) #s
87 |
88 |
89 |
90 | // Starts the logger with the username defined in the build settings.
91 | // The build setting NSLOGGER_BUILD_USERNAME is automatically configured when NSLogger is
92 | // added to a project using CocoaPods. To use it, just add this macro call to your main() function.
93 | #define LoggerStartForBuildUser() LoggerSetupBonjour(LoggerGetDefaultLogger(), NULL, CFSTR(nslogger_xstr(NSLOGGER_BUILD_USERNAME)))
94 |
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/Log4swiftTests/FunctionalTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FunctionalTests.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 19/06/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import XCTest
22 | @testable import Log4swift
23 |
24 | class FunctionalTests: XCTestCase {
25 |
26 | override func setUp() {
27 | super.setUp()
28 | LoggerFactory.sharedInstance.resetConfiguration()
29 | }
30 |
31 | func testLogToLoggerWithFormatterAndMultipleAppenders() {
32 | let formatter1 = try! PatternFormatter(identifier:"testFormatter1", pattern: "[%l][%n] %m")
33 | let formatter2 = try! PatternFormatter(identifier:"testFormatter2", pattern: "[%n][%l] %m")
34 | let appender1 = MemoryAppender()
35 | let appender2 = MemoryAppender()
36 | let logger = Logger(identifier: "test.identifier", level: .Info, appenders: [appender1, appender2])
37 | let factory = LoggerFactory.sharedInstance
38 |
39 | appender1.thresholdLevel = .Warning
40 | appender1.formatter = formatter1
41 |
42 | appender2.thresholdLevel = .Error
43 | appender2.formatter = formatter2
44 |
45 | try! factory.registerLogger(logger)
46 |
47 | // Execute
48 | Logger.getLogger("test.identifier").debug("This log to \(LogLevel.Debug) should not be printed")
49 | Logger.getLogger("test.identifier").warning{ return "This log should be printed to appender1 only"}
50 | Logger.getLogger("test.identifier").fatal("this log should be printed to both appenders")
51 | Logger.getLogger("test.identifier.sublogger").warning("this log should be printed to appender1 too")
52 |
53 | // Validate
54 | XCTAssertEqual(appender1.logMessages.count, 3, "Appender1 should have received two messages")
55 | XCTAssertEqual(appender2.logMessages.count, 1, "Appender2 should have received one messages")
56 |
57 | XCTAssertEqual(appender1.logMessages[0].message, "[\(LogLevel.Warning)][test.identifier] This log should be printed to appender1 only")
58 | XCTAssertEqual(appender1.logMessages[1].message, "[\(LogLevel.Fatal)][test.identifier] this log should be printed to both appenders")
59 | XCTAssertEqual(appender1.logMessages[2].message, "[\(LogLevel.Warning)][test.identifier.sublogger] this log should be printed to appender1 too")
60 |
61 | XCTAssertEqual(appender2.logMessages[0].message, "[test.identifier][\(LogLevel.Fatal)] this log should be printed to both appenders")
62 | }
63 |
64 | func testCurrentFileNameAndLineAndFunctionIsSentWhenLoggingString() {
65 | let formatter = try! PatternFormatter(identifier:"testFormatter", pattern: "[%F]:[%L]:[%M] %m")
66 | let appender = MemoryAppender()
67 | appender.thresholdLevel = .Debug
68 | appender.formatter = formatter
69 | let logger = Logger(identifier: "test.identifier", level: .Debug, appenders: [appender])
70 | let file = #file
71 | let function = #function
72 | let previousLine: Int
73 |
74 | // Execute
75 | previousLine = #line
76 | logger.debug("This is a debug message")
77 |
78 | // Validate
79 | XCTAssertEqual(appender.logMessages[0].message, "[\(file)]:[\(previousLine + 1)]:[\(function)] This is a debug message")
80 | }
81 |
82 | func testCurrentFileNameAndLineAndFunctionIsSentWhenLoggingStringWithFormat() {
83 | let formatter = try! PatternFormatter(identifier:"testFormatter", pattern: "[%F]:[%L]:[%M] %m")
84 | let appender = MemoryAppender()
85 | appender.thresholdLevel = .Debug
86 | appender.formatter = formatter
87 | let logger = Logger(identifier: "test.identifier", level: .Debug, appenders: [appender])
88 | let file = #file
89 | let function = #function
90 | let previousLine: Int
91 |
92 | // Execute
93 | previousLine = #line
94 | logger.debug("This is a %@ message", LogLevel.Debug.description)
95 |
96 | // Validate
97 | XCTAssertEqual(appender.logMessages[0].message, "[\(file)]:[\(previousLine + 1)]:[\(function)] This is a \(LogLevel.Debug.description) message")
98 | }
99 |
100 | func testCurrentFileNameAndLineAndFunctionIsSentWhenLoggingClosure() {
101 | let formatter = try! PatternFormatter(identifier:"testFormatter", pattern: "[%F]:[%L]:[%M] %m")
102 | let appender = MemoryAppender()
103 | appender.thresholdLevel = .Debug
104 | appender.formatter = formatter
105 | let logger = Logger(identifier: "test.identifier", level: .Debug, appenders: [appender])
106 | let file = #file
107 | let function = #function
108 | let previousLine: Int
109 |
110 | // Execute
111 | previousLine = #line
112 | logger.debug {"This is a debug message"}
113 |
114 | // Validate
115 | XCTAssertEqual(appender.logMessages[0].message, "[\(file)]:[\(previousLine + 1)]:[\(function)] This is a debug message")
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/Log4swift/Logger+convenience.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logger+convenience.swift
3 | // log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 14/06/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | /**
22 | This extension of the Logger class provides several convenience class methods to make use of log4swift easier in simple cases.
23 | */
24 | extension Logger {
25 |
26 | public class func getLogger(_ identifier: String) -> Logger {
27 | return LoggerFactory.sharedInstance.getLogger(identifier)
28 | }
29 |
30 | // MARK: Logging class methods
31 |
32 | /// Logs the provided message with a trace level using the root logger of the shared logger factory
33 | public class func trace(_ format: String, _ args: CVarArg...) {
34 | let formattedMessage = format.format(args: args)
35 | LoggerFactory.sharedInstance.rootLogger.log(message: formattedMessage, level: LogLevel.Trace)
36 | }
37 | /// Logs the provided message with a debug level using the root logger of the shared logger factory
38 | public class func debug(_ format: String, _ args: CVarArg...) {
39 | let formattedMessage = format.format(args: args)
40 | LoggerFactory.sharedInstance.rootLogger.log(message: formattedMessage, level: LogLevel.Debug)
41 | }
42 | /// Logs the provided message with a info level using the root logger of the shared logger factory
43 | public class func info(_ format: String, _ args: CVarArg...) {
44 | let formattedMessage = format.format(args: args)
45 | LoggerFactory.sharedInstance.rootLogger.log(message: formattedMessage, level: LogLevel.Info)
46 | }
47 | /// Logs the provided message with a warning level using the root logger of the shared logger factory
48 | public class func warning(_ format: String, _ args: CVarArg...) {
49 | let formattedMessage = format.format(args: args)
50 | LoggerFactory.sharedInstance.rootLogger.log(message: formattedMessage, level: LogLevel.Warning)
51 | }
52 | /// Logs the provided message with a error level using the root logger of the shared logger factory
53 | public class func error(_ format: String, _ args: CVarArg...) {
54 | let formattedMessage = format.format(args: args)
55 | LoggerFactory.sharedInstance.rootLogger.log(message: formattedMessage, level: LogLevel.Error)
56 | }
57 | /// Logs the provided message with a fatal level using the root logger of the shared logger factory
58 | public class func fatal(_ format: String, _ args: CVarArg...) {
59 | let formattedMessage = format.format(args: args)
60 | LoggerFactory.sharedInstance.rootLogger.log(message: formattedMessage, level: LogLevel.Fatal)
61 | }
62 |
63 | /// Logs a the message returned by the closer with a trace level using the root logger of the shared logger factory
64 | /// If the logger's or appender's configuration prevents the message to be issued, the closure will not be called.
65 | @nonobjc public class func trace(closure: @escaping () -> (String)) {
66 | LoggerFactory.sharedInstance.rootLogger.log(closure: closure, level: .Trace)
67 | }
68 | /// Logs a the message returned by the closer with a debug level using the root logger of the shared logger factory
69 | /// If the logger's or appender's configuration prevents the message to be issued, the closure will not be called.
70 | @nonobjc public class func debug(closure: @escaping () -> (String)) {
71 | LoggerFactory.sharedInstance.rootLogger.log(closure: closure, level: .Debug)
72 | }
73 | /// Logs a the message returned by the closer with an info level using the root logger of the shared logger factory
74 | /// If the logger's or appender's configuration prevents the message to be issued, the closure will not be called.
75 | @nonobjc public class func info(closure: @escaping () -> (String)) {
76 | LoggerFactory.sharedInstance.rootLogger.log(closure: closure, level: .Info)
77 | }
78 | /// Logs a the message returned by the closer with a warning level using the root logger of the shared logger factory
79 | /// If the logger's or appender's configuration prevents the message to be issued, the closure will not be called.
80 | @nonobjc public class func warning(closure: @escaping () -> (String)) {
81 | LoggerFactory.sharedInstance.rootLogger.log(closure: closure, level: .Warning)
82 | }
83 | /// Logs a the message returned by the closer with an error level using the root logger of the shared logger factory
84 | /// If the logger's or appender's configuration prevents the message to be issued, the closure will not be called.
85 | @nonobjc public class func error(closure: @escaping () -> (String)) {
86 | LoggerFactory.sharedInstance.rootLogger.log(closure: closure, level: .Error)
87 | }
88 | /// Logs a the message returned by the closer with a fatal level using the root logger of the shared logger factory
89 | /// If the logger's or appender's configuration prevents the message to be issued, the closure will not be called.
90 | @nonobjc public class func fatal(closure: @escaping () -> (String)) {
91 | LoggerFactory.sharedInstance.rootLogger.log(closure: closure, level: .Fatal)
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/Log4swiftTests/Logger+objectiveCTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logger+objeciveCTests.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 29/07/15.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import XCTest
22 | @testable import Log4swift
23 |
24 | class LoggerObjectiveCTests: XCTestCase {
25 |
26 | func testLoggerObjectiveCLogStringMethodsLogsAtExpectedLevel() {
27 | let appender = MemoryAppender()
28 | appender.thresholdLevel = .Trace
29 | let logger = Logger(identifier: "test.logger", level: LogLevel.Trace, appenders: [appender])
30 |
31 | // Execute
32 | logger.logTrace("trace")
33 | logger.logDebug("debug")
34 | logger.logInfo("info")
35 | logger.logWarning("warning")
36 | logger.logError("error")
37 | logger.logFatal("fatal")
38 |
39 | // Validate
40 | XCTAssertEqual(appender.logMessages[0].level, LogLevel.Trace)
41 | XCTAssertEqual(appender.logMessages[1].level, LogLevel.Debug)
42 | XCTAssertEqual(appender.logMessages[2].level, LogLevel.Info)
43 | XCTAssertEqual(appender.logMessages[3].level, LogLevel.Warning)
44 | XCTAssertEqual(appender.logMessages[4].level, LogLevel.Error)
45 | XCTAssertEqual(appender.logMessages[5].level, LogLevel.Fatal)
46 | }
47 |
48 | func testLoggerObjectiveCLogBlocMethodsLogsAtExpectedLevel() {
49 | let appender = MemoryAppender()
50 | appender.thresholdLevel = .Trace
51 | let logger = Logger(identifier: "test.logger", level: LogLevel.Trace, appenders: [appender])
52 |
53 | // Execute
54 | logger.logTraceBloc({"Trace"})
55 | logger.logDebugBloc({"Debug"})
56 | logger.logInfoBloc({"info"})
57 | logger.logWarningBloc({"warning"})
58 | logger.logErrorBloc({"error"})
59 | logger.logFatalBloc({"fatal"})
60 |
61 | // Validate
62 | XCTAssertEqual(appender.logMessages[0].level, LogLevel.Trace)
63 | XCTAssertEqual(appender.logMessages[1].level, LogLevel.Debug)
64 | XCTAssertEqual(appender.logMessages[2].level, LogLevel.Info)
65 | XCTAssertEqual(appender.logMessages[3].level, LogLevel.Warning)
66 | XCTAssertEqual(appender.logMessages[4].level, LogLevel.Error)
67 | XCTAssertEqual(appender.logMessages[5].level, LogLevel.Fatal)
68 | }
69 |
70 | func testLoggerObjectiveCLogBlocWithFileAndLineMethodsLogsWithFileAndLine() {
71 | let appender = MemoryAppender()
72 | appender.thresholdLevel = .Trace
73 | let formatter = try! PatternFormatter(identifier:"testFormatter", pattern: "[%F]:[%L]:[%M] %m")
74 | appender.formatter = formatter
75 | let logger = Logger(identifier: "test.logger", level: LogLevel.Trace, appenders: [appender])
76 |
77 | // Execute
78 | logger.logTraceBloc({"message"}, file: "filename", line: 42, function: "function")
79 | logger.logDebugBloc({"message"}, file: "filename", line: 42, function: "function")
80 | logger.logInfoBloc({"message"}, file: "filename", line: 42, function: "function")
81 | logger.logWarningBloc({"message"}, file: "filename", line: 42, function: "function")
82 | logger.logErrorBloc({"message"}, file: "filename", line: 42, function: "function")
83 | logger.logFatalBloc({"message"}, file: "filename", line: 42, function: "function")
84 |
85 | // Validate
86 | XCTAssertEqual(appender.logMessages[0].message, "[filename]:[42]:[function] message")
87 | XCTAssertEqual(appender.logMessages[1].message, "[filename]:[42]:[function] message")
88 | XCTAssertEqual(appender.logMessages[2].message, "[filename]:[42]:[function] message")
89 | XCTAssertEqual(appender.logMessages[3].message, "[filename]:[42]:[function] message")
90 | XCTAssertEqual(appender.logMessages[4].message, "[filename]:[42]:[function] message")
91 | XCTAssertEqual(appender.logMessages[5].message, "[filename]:[42]:[function] message")
92 | }
93 |
94 |
95 | func testLoggerObjectiveCLogMessageWithFileAndLineMethodsLogsWithFileAndLine() {
96 | let appender = MemoryAppender()
97 | appender.thresholdLevel = .Trace
98 | let formatter = try! PatternFormatter(identifier:"testFormatter", pattern: "[%F]:[%L]:[%M] %m")
99 | appender.formatter = formatter
100 | let logger = Logger(identifier: "test.logger", level: LogLevel.Trace, appenders: [appender])
101 |
102 | // Execute
103 | logger.logTrace("message", file: "filename", line: 42, function: "function")
104 | logger.logDebug("message", file: "filename", line: 42, function: "function")
105 | logger.logInfo("message", file: "filename", line: 42, function: "function")
106 | logger.logWarning("message", file: "filename", line: 42, function: "function")
107 | logger.logError("message", file: "filename", line: 42, function: "function")
108 | logger.logFatal("message", file: "filename", line: 42, function: "function")
109 |
110 | // Validate
111 | XCTAssertEqual(appender.logMessages[0].message, "[filename]:[42]:[function] message")
112 | XCTAssertEqual(appender.logMessages[1].message, "[filename]:[42]:[function] message")
113 | XCTAssertEqual(appender.logMessages[2].message, "[filename]:[42]:[function] message")
114 | XCTAssertEqual(appender.logMessages[3].message, "[filename]:[42]:[function] message")
115 | XCTAssertEqual(appender.logMessages[4].message, "[filename]:[42]:[function] message")
116 | XCTAssertEqual(appender.logMessages[5].message, "[filename]:[42]:[function] message")
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/Third parties/NSLogger/LoggerCommon.h:
--------------------------------------------------------------------------------
1 | /*
2 | * LoggerCommon.h
3 | *
4 | * version 1.5.1 30-DEC-2014
5 | *
6 | * Definitions common to NSLogger Viewer and NSLoggerClient
7 | * for the binary messages format
8 | * https://github.com/fpillet/NSLogger
9 | *
10 | * BSD license follows (http://www.opensource.org/licenses/bsd-license.php)
11 | *
12 | * Copyright (c) 2010-2014 Florent Pillet All Rights Reserved.
13 | *
14 | * Redistribution and use in source and binary forms, with or without modification,
15 | * are permitted provided that the following conditions are met:
16 | *
17 | * Redistributions of source code must retain the above copyright notice,
18 | * this list of conditions and the following disclaimer. Redistributions in
19 | * binary form must reproduce the above copyright notice, this list of
20 | * conditions and the following disclaimer in the documentation and/or other
21 | * materials provided with the distribution. Neither the name of Florent
22 | * Pillet nor the names of its contributors may be used to endorse or promote
23 | * products derived from this software without specific prior written
24 | * permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
25 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
26 | * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
30 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
31 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
32 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
33 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
34 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 | *
36 | */
37 |
38 | /* NSLogger native binary message format:
39 | * Each message is a dictionary encoded in a compact format. All values are stored
40 | * in network order (big endian). A message is made of several "parts", which are
41 | * typed chunks of data, each with a specific purpose (partKey), data type (partType)
42 | * and data size (partSize).
43 | *
44 | * uint32_t totalSize (total size for the whole message excluding this 4-byte count)
45 | * uint16_t partCount (number of parts below)
46 | * [repeat partCount times]:
47 | * uint8_t partKey the part key
48 | * uint8_t partType (string, binary, image, int16, int32, int64)
49 | * uint32_t partSize (only for string, binary and image types, others are implicit)
50 | * .. `partSize' data bytes
51 | *
52 | * Complete message is usually made of:
53 | * - a PART_KEY_MESSAGE_TYPE (mandatory) which contains one of the LOGMSG_TYPE_* values
54 | * - a PART_KEY_TIMESTAMP_S (mandatory) which is the timestamp returned by gettimeofday() (seconds from 01.01.1970 00:00)
55 | * - a PART_KEY_TIMESTAMP_MS (optional) complement of the timestamp seconds, in milliseconds
56 | * - a PART_KEY_TIMESTAMP_US (optional) complement of the timestamp seconds and milliseconds, in microseconds
57 | * - a PART_KEY_THREAD_ID (mandatory) the ID of the user thread that produced the log entry
58 | * - a PART_KEY_TAG (optional) a tag that helps categorizing and filtering logs from your application, and shows up in viewer logs
59 | * - a PART_KEY_LEVEL (optional) a log level that helps filtering logs from your application (see as few or as much detail as you need)
60 | * - a PART_KEY_MESSAGE which is the message text, binary data or image
61 | * - a PART_KEY_MESSAGE_SEQ which is the message sequence number (message# sent by client)
62 | * - a PART_KEY_FILENAME (optional) with the filename from which the log was generated
63 | * - a PART_KEY_LINENUMBER (optional) the linenumber in the filename at which the log was generated
64 | * - a PART_KEY_FUNCTIONNAME (optional) the function / method / selector from which the log was generated
65 | * - if logging an image, PART_KEY_IMAGE_WIDTH and PART_KEY_IMAGE_HEIGHT let the desktop know the image size without having to actually decode it
66 | */
67 |
68 | // Constants for the "part key" field
69 | #define PART_KEY_MESSAGE_TYPE 0
70 | #define PART_KEY_TIMESTAMP_S 1 // "seconds" component of timestamp
71 | #define PART_KEY_TIMESTAMP_MS 2 // milliseconds component of timestamp (optional, mutually exclusive with PART_KEY_TIMESTAMP_US)
72 | #define PART_KEY_TIMESTAMP_US 3 // microseconds component of timestamp (optional, mutually exclusive with PART_KEY_TIMESTAMP_MS)
73 | #define PART_KEY_THREAD_ID 4
74 | #define PART_KEY_TAG 5
75 | #define PART_KEY_LEVEL 6
76 | #define PART_KEY_MESSAGE 7
77 | #define PART_KEY_IMAGE_WIDTH 8 // messages containing an image should also contain a part with the image size
78 | #define PART_KEY_IMAGE_HEIGHT 9 // (this is mainly for the desktop viewer to compute the cell size without having to immediately decode the image)
79 | #define PART_KEY_MESSAGE_SEQ 10 // the sequential number of this message which indicates the order in which messages are generated
80 | #define PART_KEY_FILENAME 11 // when logging, message can contain a file name
81 | #define PART_KEY_LINENUMBER 12 // as well as a line number
82 | #define PART_KEY_FUNCTIONNAME 13 // and a function or method name
83 |
84 | // Constants for parts in LOGMSG_TYPE_CLIENTINFO
85 | #define PART_KEY_CLIENT_NAME 20
86 | #define PART_KEY_CLIENT_VERSION 21
87 | #define PART_KEY_OS_NAME 22
88 | #define PART_KEY_OS_VERSION 23
89 | #define PART_KEY_CLIENT_MODEL 24 // For iPhone, device model (i.e 'iPhone', 'iPad', etc)
90 | #define PART_KEY_UNIQUEID 25 // for remote device identification, part of LOGMSG_TYPE_CLIENTINFO
91 |
92 | // Area starting at which you may define your own constants
93 | #define PART_KEY_USER_DEFINED 100
94 |
95 | // Constants for the "partType" field
96 | #define PART_TYPE_STRING 0 // Strings are stored as UTF-8 data
97 | #define PART_TYPE_BINARY 1 // A block of binary data
98 | #define PART_TYPE_INT16 2
99 | #define PART_TYPE_INT32 3
100 | #define PART_TYPE_INT64 4
101 | #define PART_TYPE_IMAGE 5 // An image, stored in PNG format
102 |
103 | // Data values for the PART_KEY_MESSAGE_TYPE parts
104 | #define LOGMSG_TYPE_LOG 0 // A standard log message
105 | #define LOGMSG_TYPE_BLOCKSTART 1 // The start of a "block" (a group of log entries)
106 | #define LOGMSG_TYPE_BLOCKEND 2 // The end of the last started "block"
107 | #define LOGMSG_TYPE_CLIENTINFO 3 // Information about the client app
108 | #define LOGMSG_TYPE_DISCONNECT 4 // Pseudo-message on the desktop side to identify client disconnects
109 | #define LOGMSG_TYPE_MARK 5 // Pseudo-message that defines a "mark" that users can place in the log flow
110 |
111 | // Default Bonjour service identifiers
112 | #define LOGGER_SERVICE_TYPE_SSL CFSTR("_nslogger-ssl._tcp")
113 | #define LOGGER_SERVICE_TYPE CFSTR("_nslogger._tcp")
114 |
--------------------------------------------------------------------------------
/Log4swiftTests/Logger-AsynchronicityTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logger-AsynchronicityTests.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 28/10/2015.
6 | // Copyright © 2015 jerome. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Log4swift
11 |
12 | class LoggerAsynchronicityTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | LoggerFactory.sharedInstance.resetConfiguration()
17 | }
18 |
19 | override func tearDown() {
20 | super.tearDown()
21 | }
22 |
23 | func testLoggerIsSynchronousByDefault() {
24 | let rootLogger = LoggerFactory.sharedInstance.rootLogger
25 |
26 | XCTAssertFalse(rootLogger.asynchronous)
27 | }
28 |
29 | func testResetConfigurationSetsLoggerSynchronous() {
30 | let rootLogger = LoggerFactory.sharedInstance.rootLogger
31 | rootLogger.asynchronous = true
32 |
33 | // Execute
34 | rootLogger.resetConfiguration()
35 |
36 | // Validate
37 | XCTAssertFalse(rootLogger.asynchronous)
38 | }
39 |
40 | /// This test logs three message to an appender that takes 0.1 second to execute.
41 | /// It then counts the number of logged messages as soon as possible.
42 | /// If the logger is synchronous, the three logs should already be recorded.
43 | func testSynchronousLoggerLogsMessagesSynchronously() {
44 | let rootLogger = LoggerFactory.sharedInstance.rootLogger
45 | let slowAppender = MemoryAppender()
46 | slowAppender.loggingDelay = 0.1
47 | rootLogger.asynchronous = false
48 | rootLogger.appenders = [slowAppender]
49 |
50 | // execute
51 | rootLogger.error("log1")
52 | rootLogger.error("log2")
53 | rootLogger.error("log3")
54 |
55 | // Validate
56 | let loggedMessagesCount = slowAppender.logMessages.count
57 | XCTAssertEqual(loggedMessagesCount, 3, "Logged messages were not recorded synchronously (3 messages sent, \(loggedMessagesCount) recorded")
58 | }
59 |
60 | /// This test logs three message to an appender that takes 0.1 second to execute.
61 | /// It then counts the number of logged messages as soon as possible.
62 | /// If the logger is synchronous, the three logs should already be recorded.
63 | func testSynchronousLoggerLogsBlocsSynchronously() {
64 | let rootLogger = LoggerFactory.sharedInstance.rootLogger
65 | let slowAppender = MemoryAppender()
66 | slowAppender.loggingDelay = 0.1
67 | rootLogger.asynchronous = false
68 | rootLogger.appenders = [slowAppender]
69 |
70 | // execute
71 | rootLogger.error{"log1"}
72 | rootLogger.error{"log2"}
73 | rootLogger.error{"log3"}
74 |
75 | // Validate
76 | let loggedMessagesCount = slowAppender.logMessages.count
77 | XCTAssertEqual(loggedMessagesCount, 3, "Logged messages were not recorded synchronously (3 messages sent, \(loggedMessagesCount) recorded")
78 | }
79 |
80 | /// This test logs three message to an appender that takes 0.1 second to execute.
81 | /// It then counts the number of logged messages as soon as possible, and after a long enough
82 | /// delay for all messages to be logged
83 | /// If the logger is synchronous, no message should have been logged right after,
84 | /// 3 should have been after the delay.
85 | func testAsynchronousLoggerLogsMessagesAsynchronously() {
86 | let rootLogger = LoggerFactory.sharedInstance.rootLogger
87 | let slowAppender = MemoryAppender()
88 | slowAppender.loggingDelay = 0.1
89 | rootLogger.asynchronous = true
90 | rootLogger.appenders = [slowAppender]
91 |
92 | // execute
93 | rootLogger.error("log1")
94 | rootLogger.error("log2")
95 | rootLogger.error("log3")
96 |
97 | let immediateLoggedMessagesCount = slowAppender.logMessages.count
98 | self.waitUntilTrue{slowAppender.logMessages.count == 3}
99 | let delayedLoggedMessagesCount = slowAppender.logMessages.count
100 |
101 | // Validate
102 | XCTAssertEqual(immediateLoggedMessagesCount, 0, "Some messages were not logged asynchronously")
103 | XCTAssertEqual(delayedLoggedMessagesCount, 3, "Some messages were not logged after")
104 | }
105 |
106 | /// This test logs three message to an appender that takes 0.1 second to execute.
107 | /// It then counts the number of logged messages as soon as possible, and after a long enough
108 | /// delay for all messages to be logged
109 | /// If the logger is synchronous, no message should have been logged right after,
110 | /// 3 should have been after the delay.
111 | func testAsynchronousLoggerLogsBlocsAsynchronously() {
112 | let rootLogger = LoggerFactory.sharedInstance.rootLogger
113 | let slowAppender = MemoryAppender()
114 | slowAppender.loggingDelay = 0.1
115 | rootLogger.asynchronous = true
116 | rootLogger.appenders = [slowAppender]
117 |
118 | // execute
119 | rootLogger.error{"log1"}
120 | rootLogger.error{"log2"}
121 | rootLogger.error{"log3"}
122 |
123 | let immediateLoggedMessagesCount = slowAppender.logMessages.count
124 | self.waitUntilTrue{slowAppender.logMessages.count == 3}
125 | let delayedLoggedMessagesCount = slowAppender.logMessages.count
126 |
127 | // Validate
128 | XCTAssertEqual(immediateLoggedMessagesCount, 0, "Some messages were not logged asynchronously")
129 | XCTAssertEqual(delayedLoggedMessagesCount, 3, "Some messages were not logged after delay")
130 | }
131 |
132 | func testMessagesSentToAsynchronousLoggersAreOrdered() {
133 | let logger1 = LoggerFactory.sharedInstance.getLogger("logger1")
134 | logger1.asynchronous = true
135 | let logger2 = LoggerFactory.sharedInstance.getLogger("logger2")
136 | logger2.asynchronous = true
137 |
138 | let slowAppender = MemoryAppender()
139 | logger1.appenders = [slowAppender]
140 | logger2.appenders = [slowAppender]
141 |
142 | // Execute
143 | logger2.info{ Thread.sleep(forTimeInterval: 0.2); return "1"; }
144 | logger1.info{ Thread.sleep(forTimeInterval: 0.1); return "2"; }
145 | logger2.info{ Thread.sleep(forTimeInterval: 0.2); return "3"; }
146 | logger1.info{ Thread.sleep(forTimeInterval: 0.1); return "4"; }
147 |
148 | self.waitUntilTrue{slowAppender.logMessages.count == 4}
149 |
150 | // Validate
151 | let expectedOrderedMessages: [LoggedMessage] = [
152 | ("1", .Info),
153 | ("2", .Info),
154 | ("3", .Info),
155 | ("4", .Info)]
156 |
157 | XCTAssertEqual(slowAppender.logMessages.count, expectedOrderedMessages.count, "All messages were not logged")
158 | for index in 0...expectedOrderedMessages.count - 1 {
159 | XCTAssertTrue(slowAppender.logMessages[index] == expectedOrderedMessages[index], "Order of logged messages is not correct")
160 | }
161 | }
162 |
163 | //MARK: private methods
164 |
165 | fileprivate func waitUntilTrue(_ conditionClosure: () -> Bool) {
166 | let timeout = 5.0
167 | let loopDelay = 0.1
168 | var loopCounter = 0
169 | while(conditionClosure() == false && timeout > (loopDelay * Double(loopCounter))) {
170 | Thread.sleep(forTimeInterval: loopDelay)
171 | loopCounter += 1
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/Log4swift/LoggerFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoggerFactory.swift
3 | // log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 14/06/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import Foundation
22 |
23 | /**
24 | The logger factory is responsible for
25 | * loading configuration from files or dictionaries
26 | * holding the loggers and appenders
27 | * matching UTI identifiers to loggers
28 | */
29 | @objc public final class LoggerFactory: NSObject {
30 | @objc static public let sharedInstance = LoggerFactory()
31 |
32 | /// Errors that can be thrown by logger factory
33 | public enum LoggerError: Error {
34 | case InvalidLoggerIdentifier
35 | }
36 |
37 | internal var configurationFileObserver: FileObserver?
38 |
39 | /// The root logger is the catchall logger used when no other logger matches. It is the only non-optional logger of the factory.
40 | public let rootLogger = Logger()
41 | internal var loggers = Dictionary()
42 |
43 | // MARK: Configuration
44 |
45 | /// Adds the given logger to the list of available loggers. If a logger with the same identifier already exists, it will be replaced by the new one.
46 | /// Adding a logger with an empty identifier will cause an error. Use the root logger instead of defining a logger with an empty identifier.
47 | @objc public func registerLogger(_ newLogger: Logger) throws {
48 | if(newLogger.identifier.isEmpty) {
49 | throw LoggerError.InvalidLoggerIdentifier
50 | }
51 |
52 | self.loggers[newLogger.identifier] = newLogger
53 | self.clearAutoGeneratedLoggers()
54 | }
55 |
56 | @objc public func resetConfiguration() {
57 | self.loggers.removeAll()
58 | self.rootLogger.resetConfiguration()
59 | }
60 |
61 | // MARK: Acccessing loggers
62 |
63 | /// Helper method to easily get logger.
64 | /// This is equivalent to LoggerFactory.sharedInstance.getLogger(identifier)
65 | @objc public class func getLogger(identifier: String) -> Logger {
66 | return self.sharedInstance.getLogger(identifier)
67 | }
68 |
69 | /// Returns the logger for the given identifier.
70 | /// If an exact match is found, the associated logger will be returned. If not, a new logger will be created on the fly base on the logger with with the longest maching identifier.
71 | /// Ultimately, if no logger is found, the root logger will be used as a base.
72 | /// Once the logger has been created, it is associated with its identifier, and can be updated independently from other loggers.
73 | @objc public func getLogger(_ identifierToFind: String) -> Logger {
74 | let foundLogger: Logger
75 |
76 | if let loggerFromCache = self.loggers[identifierToFind] {
77 | foundLogger = loggerFromCache
78 | } else {
79 | var reducedIdentifier = identifierToFind.stringByRemovingLastComponent(withDelimiter: ".") ?? ""
80 | var loggerToCopy = self.rootLogger
81 | while (loggerToCopy === self.rootLogger && !reducedIdentifier.isEmpty) {
82 | if let loggerFromCache = self.loggers[String(reducedIdentifier)] {
83 | loggerToCopy = loggerFromCache
84 | }
85 | reducedIdentifier = reducedIdentifier.stringByRemovingLastComponent(withDelimiter: ".") ?? ""
86 | }
87 |
88 | foundLogger = Logger(parentLogger: loggerToCopy, identifier: identifierToFind)
89 | self.loggers[identifierToFind] = foundLogger
90 | }
91 |
92 | return foundLogger
93 | }
94 |
95 | private func clearAutoGeneratedLoggers() {
96 | for (key, logger) in self.loggers {
97 | if(logger.parent != nil) {
98 | self.loggers.removeValue(forKey: key)
99 | }
100 | }
101 | }
102 | }
103 |
104 | extension LoggerFactory {
105 |
106 | /**
107 | Configures the root logger to output logs to the Xcode console.
108 | Logs coloring will be enabled if you have XcodeColors installed.
109 | This configuration is not meant to be used for production.
110 |
111 | **This method will replace your current configuration by a new one.**
112 | */
113 | public func configureForXcodeConsole(thresholdLevel: LogLevel = .Debug) {
114 | self.resetConfiguration()
115 |
116 | let xcodeAppender = StdOutAppender("xcodeAppender")
117 | xcodeAppender.thresholdLevel = thresholdLevel
118 | xcodeAppender.errorThresholdLevel = .Debug
119 | xcodeAppender.setTextColor(.DarkRed, forLevel: .Fatal)
120 | xcodeAppender.setTextColor(.Red, forLevel: .Error)
121 | xcodeAppender.setTextColor(.Orange, forLevel: .Warning)
122 | xcodeAppender.setTextColor(.Blue, forLevel: .Info)
123 | xcodeAppender.setTextColor(.DarkGrey, forLevel: .Debug)
124 | xcodeAppender.setTextColor(.LightGrey, forLevel: .Trace)
125 |
126 | do {
127 | let formatter = try PatternFormatter(identifier: "xcodeFormatter", pattern: "%d{'format':'%F %T'} %m")
128 | xcodeAppender.formatter = formatter
129 | } catch {
130 | // we apply no formatter if an error occures (this should never happen)
131 | NSLog("Could not set the formatter for the XCodeConsole configuration : \(error)")
132 | }
133 |
134 | self.rootLogger.appenders = [xcodeAppender]
135 | }
136 |
137 | /**
138 | Configures the root logger to output logs to the system logging system, making them available in the "Console" application.
139 | This configuration is suitable for production use.
140 |
141 | **This method will replace your current configuration by a new one.**
142 | */
143 | public func configureForSystemConsole(thresholdLevel: LogLevel = .Warning) {
144 | self.resetConfiguration()
145 |
146 | let systemConsoleAppender = ASLAppender("systemConsoleAppender")
147 | systemConsoleAppender.thresholdLevel = thresholdLevel
148 |
149 | self.rootLogger.appenders = [systemConsoleAppender]
150 | }
151 |
152 | /**
153 | Configures the root logger to output logs to NSLogger. SSL and local cache will be enabled on the appender.
154 | This configuration is not meant to be used for production.
155 |
156 | **This method will replace your current configuration by a new one.**
157 | */
158 | #if !os(watchOS)
159 | public func configureForNSLogger(remoteHost: String = "127.0.0.1", remotePort: UInt32 = 50000, thresholdLevel: LogLevel = .Debug) {
160 | self.resetConfiguration()
161 |
162 | let nsloggerAppender = NSLoggerAppender(identifier: "nsloggerAppender", remoteHost: remoteHost, remotePort: remotePort, useLocalCache: true, useSSL: true)
163 | nsloggerAppender.thresholdLevel = thresholdLevel
164 |
165 | self.rootLogger.appenders = [nsloggerAppender]
166 | }
167 | #endif
168 | }
169 |
--------------------------------------------------------------------------------
/Log4swift/Appenders/NSLoggerAppender.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSLoggerAppender.swift
3 | // Log4swift
4 | //
5 | // Created by Jérôme Duquennoy on 16/06/2015.
6 | // Copyright © 2015 Jérôme Duquennoy. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | import Foundation
22 |
23 | /**
24 | The NSLogger appender relies on the NSLogger project (see https://github.com/fpillet/NSLogger) to send log messages over the network.
25 | */
26 | @available(iOS 8.0, *)
27 | public class NSLoggerAppender : Appender {
28 | public enum DictionaryKey: String {
29 | case BonjourServiceName = "BonjourServiceName"
30 | case UseLocalCache = "UseLocalCache"
31 | case UseSSL = "UseSSL"
32 | case RemoteHost = "RemoteHost"
33 | case RemotePort = "RemotePort"
34 | }
35 |
36 | let logger: UnsafeMutablePointer
37 |
38 | /// This initializer will configure the NSLogger client to send the messages to a specific host, with a specific port.
39 | /// Parameters are :
40 | /// * remoteHost : the remote host address, as an IP or a resolable host name. Default value is 127.0.0.1.
41 | /// * remotePort : the number of the TCP port to which the client will connect Default value is 50 000.
42 | /// * useLocalCache : keep messages in memory as long as the remote viewer is not reachable Default value is true.
43 | /// * useSSL: as you can expect, the client will initiate an SSL connection. Default value is true.
44 | public convenience init(identifier: String, remoteHost: String = "127.0.0.1", remotePort: UInt32 = 50000, useLocalCache: Bool = true, useSSL: Bool = true) {
45 | self.init(identifier)
46 | setupTcpLogger(remoteHost: remoteHost, remotePort: remotePort, useLocalCache: useLocalCache, useSSL: useSSL)
47 | }
48 |
49 | /// This initializer will configure the NSLogger client to send the message to a Bonjour service provider.
50 | /// * bonjourServiceName : the name of the bonjour service
51 | /// * useLocalCache : keep messages in memory as long as the remote viewer is not reachable Default value is true.
52 | /// * useSSL: as you can expect, the client will initiate an SSL connection. Default value is true.
53 | public convenience init(identifier: String, bonjourServiceName: String, useLocalCache: Bool = true, useSSL: Bool = true) {
54 | self.init(identifier)
55 | setupBonjourLogger(bonjourServiceName: bonjourServiceName, useLocalCache: useLocalCache, useSSL: useSSL)
56 | }
57 |
58 | public required init(_ identifier: String) {
59 | self.logger = LoggerInit()
60 | super.init(identifier)
61 | }
62 |
63 | public override func update(withDictionary dictionary: Dictionary, availableFormatters: Array) throws {
64 | try super.update(withDictionary: dictionary, availableFormatters: availableFormatters)
65 |
66 | let bonjourMode = (dictionary[DictionaryKey.BonjourServiceName.rawValue] != nil)
67 |
68 | let useLocalCache: Bool
69 | let useSSL: Bool
70 |
71 | try super.update(withDictionary: dictionary, availableFormatters: availableFormatters)
72 |
73 | if let safeUseLocalCache = (dictionary[DictionaryKey.UseLocalCache.rawValue] as? String) {
74 | useLocalCache = Bool(safeUseLocalCache)
75 | } else {
76 | useLocalCache = true
77 | }
78 |
79 | if let safeUseSSLString = (dictionary[DictionaryKey.UseSSL.rawValue] as? String) {
80 | useSSL = Bool(safeUseSSLString)
81 | } else {
82 | useSSL = true
83 | }
84 |
85 | if(bonjourMode) {
86 | let serviceName: String
87 |
88 | if let safeServiceName = (dictionary[DictionaryKey.BonjourServiceName.rawValue] as? String) {
89 | serviceName = safeServiceName
90 | } else {
91 | throw NSError.Log4swiftError(description: "Missing 'BonjourServiceName' parameter for NSLogger appender '\(self.identifier)'")
92 | }
93 |
94 | setupBonjourLogger(bonjourServiceName: serviceName, useLocalCache: useLocalCache, useSSL: useSSL)
95 | } else {
96 | let remoteHost: String
97 | let remotePort: UInt32
98 |
99 | if let safeRemoteHost = (dictionary[DictionaryKey.RemoteHost.rawValue] as? String) {
100 | remoteHost = safeRemoteHost
101 | } else {
102 | remoteHost = "placeholder"
103 | throw NSError.Log4swiftError(description: "Missing 'RemoteHost' parameter for NSLogger appender '\(self.identifier)'")
104 | }
105 |
106 | if let safeRemotePort = dictionary[DictionaryKey.RemotePort.rawValue] as? Int {
107 | remotePort = UInt32(safeRemotePort)
108 | } else if let safeRemotePortString = (dictionary[DictionaryKey.RemotePort.rawValue] as? String) {
109 | if let safeRemotePort = UInt32(safeRemotePortString) {
110 | remotePort = safeRemotePort
111 | } else {
112 | remotePort = 0
113 | throw NSError.Log4swiftError(description: "Non numeric string 'RemotePort' parameter for NSLogger appender '\(self.identifier)'")
114 | }
115 | } else {
116 | remotePort = 50000
117 | }
118 | if(remotePort < 1024 || remotePort > 65535) {
119 | throw NSError.Log4swiftError(description: "RemotePort should be between 1024 and 65535 for NSLogger appender '\(self.identifier)'")
120 | }
121 |
122 | setupTcpLogger(remoteHost: remoteHost, remotePort: remotePort, useLocalCache: useLocalCache, useSSL: useSSL)
123 | }
124 | }
125 |
126 | deinit {
127 | LoggerStop(self.logger)
128 | }
129 |
130 | public override func performLog(_ log: String, level: LogLevel, info: LogInfoDictionary) {
131 | var loggerId = ""
132 | if let safeLoggerId = info[LogInfoKeys.LoggerName] {
133 | loggerId = safeLoggerId.description
134 | }
135 | LogMessageRawToF(self.logger, nil, 0, nil, loggerId, Int32(level.rawValue), log)
136 | }
137 |
138 | private func setupBonjourLogger(bonjourServiceName: String, useLocalCache: Bool, useSSL: Bool) {
139 | var options = UInt32(kLoggerOption_BrowseBonjour)
140 | if(useLocalCache) {
141 | options |= UInt32(kLoggerOption_BufferLogsUntilConnection)
142 | }
143 | if(useSSL) {
144 | options |= UInt32(kLoggerOption_UseSSL)
145 | }
146 | LoggerSetOptions(self.logger, options)
147 |
148 | LoggerSetupBonjour(self.logger, nil, bonjourServiceName as NSString)
149 |
150 | LoggerStart(self.logger)
151 | }
152 |
153 | private func setupTcpLogger(remoteHost: String, remotePort: UInt32, useLocalCache: Bool, useSSL: Bool) {
154 | var options = UInt32(0)
155 | if(useLocalCache) {
156 | options |= UInt32(kLoggerOption_BufferLogsUntilConnection)
157 | }
158 | if(useSSL) {
159 | options |= UInt32(kLoggerOption_UseSSL)
160 | }
161 | LoggerSetOptions(self.logger, options)
162 |
163 | LoggerSetViewerHost(self.logger, remoteHost as NSString, remotePort)
164 |
165 | LoggerStart(self.logger)
166 | }
167 | }
168 |
--------------------------------------------------------------------------------