├── 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 | --------------------------------------------------------------------------------