Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: emaloney/CleanroomLogger
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: codelathe/CleanroomLogger
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Able to merge. These branches can be automatically merged.
  • 11 commits
  • 5 files changed
  • 2 contributors

Commits on May 24, 2022

  1. Add pod spec file

    AlexanderMarchant authored May 24, 2022
    Copy the full SHA
    0a74291 View commit details

Commits on May 26, 2022

  1. Copy the full SHA
    027ecfb View commit details
  2. Copy the full SHA
    fb6f1e7 View commit details
  3. Add support for rolling log files once they are too large

    - Once a log file meets the maximum size, create a new log file to continue logging
    - Update pruning the files to handle rolled files and not delete them
    - Update logFilename generation to incorporate the rolled log file number
    Alex-Marchant committed May 26, 2022
    Copy the full SHA
    123cddb View commit details
  4. Copy the full SHA
    279528b View commit details
  5. Copy the full SHA
    50b47ea View commit details
  6. Merge pull request #1 from codelathe/add-rolling-log-support

    Support rolling log files
    AlexanderMarchant authored May 26, 2022
    Copy the full SHA
    320d7ec View commit details

Commits on Nov 29, 2022

  1. Add disable function

    Alex-Marchant committed Nov 29, 2022
    Copy the full SHA
    7597bf2 View commit details
  2. Copy the full SHA
    7b33c1a View commit details

Commits on Nov 30, 2022

  1. Merge pull request #2 from AlexanderMarchant/add-ability-to-disable-l…

    …ogger
    
    Add ability to disable logger
    AlexanderMarchant authored Nov 30, 2022
    Copy the full SHA
    7784ddf View commit details
  2. Copy the full SHA
    e8d4dba View commit details
Showing with 174 additions and 15 deletions.
  1. +16 −0 CleanroomLogger.podspec
  2. +3 −1 CleanroomLogger.xcodeproj/project.pbxproj
  3. +22 −0 Sources/Log.swift
  4. +5 −2 Sources/RotatingLogFileConfiguration.swift
  5. +128 −12 Sources/RotatingLogFileRecorder.swift
16 changes: 16 additions & 0 deletions CleanroomLogger.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Pod::Spec.new do |s|
s.name = 'CleanroomLogger'
s.version = '7.0.1'
s.summary = 'Extensible Swift-based logging API that is simple, lightweight and performant'
s.homepage = 'https://github.com/emaloney/CleanroomLogger'
s.author = 'emaloney'
s.source = { :git => 'https://github.com/emaloney/CleanroomLogger.git', :tag => s.version }
s.ios.deployment_target = "9.0"
s.watchos.deployment_target = "4.0"
s.tvos.deployment_target = "12.0"
s.osx.deployment_target = "10.10"
s.source_files = 'Sources/*.swift'
s.license = 'MIT'

s.swift_version = '5.0'
end
4 changes: 3 additions & 1 deletion CleanroomLogger.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
@@ -311,7 +311,6 @@
TargetAttributes = {
3B9059021DAECB5200B4EEC0 = {
CreatedOnToolsVersion = 8.0;
DevelopmentTeam = MTP6A36P8K;
LastSwiftMigration = 0900;
ProvisioningStyle = Automatic;
};
@@ -328,6 +327,7 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = 3B9058F91DAECB5200B4EEC0;
@@ -548,6 +548,7 @@
baseConfigurationReference = 3B9059251DAECB9E00B4EEC0 /* Debug.xcconfig */;
buildSettings = {
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 35;
DYLIB_INSTALL_NAME_BASE = "@rpath";
@@ -562,6 +563,7 @@
baseConfigurationReference = 3B9059281DAECB9E00B4EEC0 /* Release.xcconfig */;
buildSettings = {
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 35;
DYLIB_INSTALL_NAME_BASE = "@rpath";
22 changes: 22 additions & 0 deletions Sources/Log.swift
Original file line number Diff line number Diff line change
@@ -233,6 +233,28 @@ public struct Log
}
logLock.unlock()
}

/**
Disables the logger, setting all existing channels to nil.

Disabling the logger allows you to restart/reconfigure the logger in real-time without the need to restart the whole app.
*/
public static func disable()
{
logLock.lock()

if didEnable
{
self.error = nil
self.warning = nil
self.info = nil
self.debug = nil
self.verbose = nil
didEnable = false
}

logLock.unlock()
}

private static let logLock = NSLock()
private static var didEnable = false
7 changes: 5 additions & 2 deletions Sources/RotatingLogFileConfiguration.swift
Original file line number Diff line number Diff line change
@@ -56,10 +56,13 @@ open class RotatingLogFileConfiguration: BasicLogConfiguration
sequence, and the formatted string returned by the first formatter to
yield a non-`nil` value will be recorded. If every formatter returns `nil`,
the log entry is silently ignored and not recorded.

- parameter maximumFileSize: The approximate maximum size (in bytes) to allow log files to grow.
If a log file is larger than this value after a log statement is appended, then the log file is rolled.
*/
public init(minimumSeverity: LogSeverity, daysToKeep: Int, directoryPath: String, synchronousMode: Bool = false, filters: [LogFilter] = [], formatters: [LogFormatter] = [ReadableLogFormatter()])
public init(minimumSeverity: LogSeverity, daysToKeep: Int, directoryPath: String, synchronousMode: Bool = false, filters: [LogFilter] = [], formatters: [LogFormatter] = [ReadableLogFormatter()], maximumFileSize: Int64? = nil)
{
logFileRecorder = RotatingLogFileRecorder(daysToKeep: daysToKeep, directoryPath: directoryPath, formatters: formatters)
logFileRecorder = RotatingLogFileRecorder(daysToKeep: daysToKeep, directoryPath: directoryPath, formatters: formatters, maximumFileSize: maximumFileSize)

super.init(minimumSeverity: minimumSeverity, filters: filters, recorders: [logFileRecorder], synchronousMode: synchronousMode)
}
140 changes: 128 additions & 12 deletions Sources/RotatingLogFileRecorder.swift
Original file line number Diff line number Diff line change
@@ -25,21 +25,27 @@ open class RotatingLogFileRecorder: LogRecorderBase
/** The filesystem path to a directory where the log files will be
stored. */
public let directoryPath: String

/** The approximate maximum size (in bytes) to allow log files to grow.
If a log file is larger than this value after a log statement is appended,
then the log file is rolled. */
public let maximumFileSize: Int64?

private static let filenameFormatter: DateFormatter = {
let fmt = DateFormatter()
fmt.dateFormat = "yyyy-MM-dd'.log'"
fmt.dateFormat = "yyyy-MM-dd"
return fmt
}()

private var mostRecentLogTime: Date?
private var currentFileRecorder: FileLogRecorder?
private static var currentNumberOfRolledFiles: Int?

/**
Initializes a new `RotatingLogFileRecorder` instance.

- warning: The `RotatingLogFileRecorder` expects to have full control over
the contents of its `directoryPath`. Any file not recognized as an active
the contents of its `directoryPath`. Any file not recognized as an active
log file will be deleted during the automatic pruning process, which may
occur at any time. Therefore, be __extremely careful__ when constructing
the value passed in as the `directoryPath`.
@@ -56,11 +62,15 @@ open class RotatingLogFileRecorder: LogRecorderBase
sequence, and the formatted string returned by the first formatter to
yield a non-`nil` value will be recorded. If every formatter returns `nil`,
the log entry is silently ignored and not recorded.

- parameter maximumFileSize: The approximate maximum size (in bytes) to allow log files to grow.
If a log file is larger than this value after a log statement is appended, then the log file is rolled.
*/
public init(daysToKeep: Int, directoryPath: String, formatters: [LogFormatter] = [ReadableLogFormatter()])
public init(daysToKeep: Int, directoryPath: String, formatters: [LogFormatter] = [ReadableLogFormatter()], maximumFileSize: Int64? = nil)
{
self.daysToKeep = daysToKeep
self.directoryPath = directoryPath
self.maximumFileSize = maximumFileSize

super.init(formatters: formatters)
}
@@ -73,24 +83,109 @@ open class RotatingLogFileRecorder: LogRecorderBase

- returns: The filename.
*/
open class func logFilename(forDate date: Date)
open class func logFilename(forDate date: Date, rolledLogFileNumber: Int? = nil, withExtension: Bool = true)
-> String
{
return filenameFormatter.string(from: date)
guard let rolledLogFileNumber = rolledLogFileNumber,
rolledLogFileNumber > 0 else
{
return "\(filenameFormatter.string(from: date))\(withExtension ? ".log" : "")"
}
return "\(filenameFormatter.string(from: date))(\(rolledLogFileNumber))\(withExtension ? ".log" : "")"
}

/**
Returns a bool defining whether the size of the file at the provided path is greater than the provided file size

- parameter fileSize: The file size `(in bytes)` that is used as the max size for file
- parameter path: The path to the file to be checked

private class func fileLogRecorder(_ date: Date, directoryPath: String, formatters: [LogFormatter])
- returns: A bool indicating if the file exceeds the given file size.
*/
private class func hasExceeded(fileSize: Int64, at path: String) -> Bool
{
guard let fileAttributes = try? FileManager.default.attributesOfItem(atPath: path),
let bytes = fileAttributes[.size] as? Int64,
bytes >= fileSize else
{
return false
}

return true
}

private class func fileLogRecorder(_ date: Date, directoryPath: String, formatters: [LogFormatter], maximumFileSize: Int64? = nil)
-> FileLogRecorder?
{
let fileName = logFilename(forDate: date)
let filePath = (directoryPath as NSString).appendingPathComponent(fileName)
let fileNameWithoutExtension = logFilename(forDate: date, withExtension: false)
var fileName = logFilename(forDate: date)
var filePath = (directoryPath as NSString).appendingPathComponent(fileName)

guard let maximumFileSize = maximumFileSize else
{
return FileLogRecorder(filePath: filePath, formatters: formatters)
}

if FileManager.default.fileExists(atPath: filePath)
{
// Check if the first file is larger than the maxLogSize
guard hasExceeded(fileSize: maximumFileSize, at: filePath) else
{
return FileLogRecorder(filePath: filePath, formatters: formatters)
}

if let currentNumberOfRolledFiles = self.currentNumberOfRolledFiles
{
let nextRolledNumber = currentNumberOfRolledFiles + 1

fileName = logFilename(forDate: date, rolledLogFileNumber: nextRolledNumber)
filePath = (directoryPath as NSString).appendingPathComponent(fileName)
self.currentNumberOfRolledFiles = nextRolledNumber
return FileLogRecorder(filePath: filePath, formatters: formatters)
}
else
{
// Identify the current number of rolled log files for this date
let directoryContents = try? FileManager.default.contentsOfDirectory(atPath: directoryPath)
.filter { return $0 != fileName }
.filter { return $0.contains(fileNameWithoutExtension) }
.sorted()

guard directoryContents?.isEmpty == false else
{
// If no rolled files, start with 1
fileName = logFilename(forDate: date, rolledLogFileNumber: 1)
filePath = (directoryPath as NSString).appendingPathComponent(fileName)
self.currentNumberOfRolledFiles = 1
return FileLogRecorder(filePath: filePath, formatters: formatters)
}

// Check if the newest rolled file exceeds the limit or not
let newestFilePath = (directoryPath as NSString).appendingPathComponent(directoryContents!.last!)
guard hasExceeded(fileSize: maximumFileSize, at: newestFilePath) else
{
self.currentNumberOfRolledFiles = directoryContents!.count
fileName = logFilename(forDate: date, rolledLogFileNumber: directoryContents!.count)
filePath = (directoryPath as NSString).appendingPathComponent(fileName)
return FileLogRecorder(filePath: filePath, formatters: formatters)
}

// If it does, create a new file
// +1 because the initial (non-rolled) file has been filtered from the directoryContents
fileName = logFilename(forDate: date, rolledLogFileNumber: directoryContents!.count + 1)
filePath = (directoryPath as NSString).appendingPathComponent(fileName)
self.currentNumberOfRolledFiles = directoryContents!.count + 1

}
}

return FileLogRecorder(filePath: filePath, formatters: formatters)
}

private func fileLogRecorder(_ date: Date)
-> FileLogRecorder?
{
return type(of: self).fileLogRecorder(date, directoryPath: directoryPath, formatters: formatters)
return type(of: self).fileLogRecorder(date, directoryPath: directoryPath, formatters: formatters, maximumFileSize: self.maximumFileSize)
}

private func isDate(_ firstDate: Date, onSameDayAs secondDate: Date)
@@ -100,6 +195,20 @@ open class RotatingLogFileRecorder: LogRecorderBase
let secondDateStr = type(of: self).logFilename(forDate: secondDate)
return firstDateStr == secondDateStr
}

/**
Checks if the current log file exceeds the maximum file size allowed, if it does, the log files are rolled.

- parameter entry: the log entry to base the new log file off if required
*/
private func rollLogFileIfNeeded(_ entry: LogEntry)
{
guard let maximumFileSize = self.maximumFileSize,
let filePath = currentFileRecorder?.filePath,
RotatingLogFileRecorder.hasExceeded(fileSize: maximumFileSize, at: filePath) else { return }

currentFileRecorder = fileLogRecorder(entry.timestamp)
}

/**
Attempts to create—if it does not already exist—the directory indicated
@@ -140,6 +249,8 @@ open class RotatingLogFileRecorder: LogRecorderBase
mostRecentLogTime = entry.timestamp as Date

currentFileRecorder?.record(message: message, for: entry, currentQueue: queue, synchronousMode: synchronousMode)

rollLogFileIfNeeded(entry)
}

/**
@@ -156,18 +267,18 @@ open class RotatingLogFileRecorder: LogRecorderBase
var date = Date()
var filesToKeep = Set<String>()
for _ in 0..<daysToKeep {
let filename = type(of: self).logFilename(forDate: date)
let filename = type(of: self).logFilename(forDate: date, withExtension: false)
filesToKeep.insert(filename)
date = cal.date(byAdding: .day, value: -1, to: date, wrappingComponents: true)!
}

do {
let fileMgr = FileManager.default
let filenames = try fileMgr.contentsOfDirectory(atPath: directoryPath)

let pathsToRemove = filenames
.filter { return !$0.hasPrefix(".") }
.filter { return !filesToKeep.contains($0) }
.filter { return !$0.contains(Array(filesToKeep)) }
.map { return (self.directoryPath as NSString).appendingPathComponent($0) }

for path in pathsToRemove {
@@ -185,3 +296,8 @@ open class RotatingLogFileRecorder: LogRecorderBase
}
}

extension String {
func contains(_ strings: [String]) -> Bool {
strings.contains { contains($0) }
}
}