Welcome back to the second part of our series on setting up logging correctly with CocoaLumberjack in iOS.
In the previous blog, we covered the basics of CocoaLumberjack and logging into the console and file, and its use with Crashlytics.
If you have missed the previous blog then please read it before starting reading this advanced version of it,
I already explained CocoaLumberjeck briefly in the previous blog, So let’s move forward quickly by skipping details.
Develop a growth mindset that sees failures as opportunities for growth with Justly today.
Logging is an essential part of app development, providing valuable insights for debugging, analysis, and support purposes. CocoaLumberjack is a popular logging framework that offers a range of powerful features to customize and manage logs effectively.
Using the advanced features will enable us to take our logging to the next level, providing enhanced readability, efficient log management, and centralized log analysis.
In this blog, we will learn how to zip the log files and upload them to the server, and send logs to the AWS CloudWatch.
Let’s dive in!
Archiving and sharing logs can be essential for debugging or analysis purposes.
CocoaLumberjack, in combination with the SSZipArchive
library, allows us to zip log files and upload them.
SSZipArchive
library to your project using CocoaPods or manually.Logs
that contains log files.If we consider our previous blog example, we have added the DDFileLoggerProvider.swift
file to customize the logging methods.
Let’s add new methods in the same class.
First, we have to create a directory where we can store the log file.
func createZipDirectory() {
let path = NSTemporaryDirectory() + "/Logs"
if !FileManager.default.fileExists(atPath: path) {
do {
try FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
} catch {
LogE("DDFileLoger : Unable to create directory at:\(path), error:\(error)")
}
}
}
Then let’s zip the log file at the predefined location.
func zipLogFiles() -> URL {
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd-hh-mm-ss"
let now = df.string(from: Date())
let destination = FileManager.default.temporaryDirectory.appendingPathComponent("Logs/\(now).zip")
SSZipArchive.createZipFile(atPath: destination.path, withContentsOfDirectory: logFileManager.logsDirectory)
return destination
}
After zipping the log files, you can choose how to upload them, such as using an API or SDK specific to your desired destination, like Amazon S3. For instance, using the AWS SDK for iOS.
Here, our goal is to allow users to send feedback reports when they encounter bugs, crashes, or any other abnormalities in the app.
To achieve this, we need to collect all the logs from the app and combine them into a single file, which will be sent to the developer using an API or SDK such as Amazon S3.
In this example, we’ll focus on sending the logs to the server via an API call. Let’s assume we have a “Report Problem” screen with a submit button. When the user taps on the submit button, we need to follow these steps:
Note: I’m adding changes to the same example that we created for the previous blog.
let logger = DDFileLoggerProvider().provideLogger()
func removeAllZipLogs() {
let fileManager = FileManager.default
let logsDir = fileManager.temporaryDirectory.appendingPathComponent("Logs")
do {
let fileURLs = try fileManager.contentsOfDirectory(at: logsDir,
includingPropertiesForKeys: nil,
options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants])
try fileURLs.forEach({ fileURL in
if fileURL.pathExtension == "zip" {
try FileManager.default.removeItem(at: fileURL)
}
})
} catch {
LogE("DDFileLogger : remove all zip error \(error)")
}
}
func submitReport() {
logger.removeAllZipLogs() // remove previously created zip files
logger.createZipDirectory() // create a zip directory
let logFilePath = logger.zipLogs() // create a fresh zip file
.
.
. // Upload this log file to a server via an API call.
}
With this log file upload, if we want to send any device-related data like device version, device name, system’s current version, device type, etc then also we can send them with this log file to the server.
Integrating CocoaLumberjack with AWS CloudWatch allows us to centralize log management and gain insights into our application’s behavior.
To upload logs to AWS CloudWatch, the following steps need to be followed:
MyLogFormatter
class which we already have defined in our previous blog.To use AWS CloudWatch for logging, we would typically need to specify the log group and stream name where the logs will be sent. This information helps organize and categorize the logs within AWS CloudWatch.
Create a default service configuration by adding the following code snippet in the application:didFinishLaunchingWithOptions:
application delegate method.
// Set up AWS credentials
let credentialsProvider = AWSStaticCredentialsProvider(accessKey: "YOUR_AWS_ACCESS_KEY", secretKey: "YOUR_AWS_SECRET_KEY")
let configuration = AWSServiceConfiguration(region: .APSouth1, credentialsProvider: credentialsProvider)
// Register the configuration with AWSLogs
AWSServiceManager.default().defaultServiceConfiguration = configuration
For the logging, if you want to change the log level to define that the logs should be sent only above the set level, then you can change it like this,
AWSDDLog.sharedInstance.logLevel = .verbose
For setting up the group and stream with the cloud watch add the below code in one function and call that function in the app delegate.
// Add contants for log group and stream name
let LOG_GROUP = "YOUR_LOG_GROUP_NAME"
let LOG_STREAM = "YOUR_LOG_STREAM_NAME"
func createLogGroup() {
// Create an instance of AWSLogs using the registered configuration
let logsClient = AWSLogs.default()
// Create a log group request
let logGroupRequest = AWSLogsCreateLogGroupRequest()
logGroupRequest?.logGroupName = LOG_GROUP
logsClient.createLogGroup(logGroupRequest!) { error in
if let error {
print("Error creating log group: \(error.localizedDescription)")
}
}
}
func createLogStream() {
let logsClient = AWSLogs.default()
// Create a log stream request
let logStreamRequest = AWSLogsCreateLogStreamRequest()
logStreamRequest?.logStreamName = LOG_STREAM
logsClient.createLogStream(logStreamRequest!) { error in
if let error {
print("Error creating log stream: \(error.localizedDescription)")
}
}
}
Note: Log stream is optional to create, as AWS will take the default log stream name if we do not provide it.
Now we need to add the main thing for adding logs to AWS Cloudwatch.
func createLogEvent(message: String) {
// Create an instance of AWSLogs using the registered configuration
// Allows us to interact with the CloudWatch Logs service.
let logsClient = AWSLogs.default()
let logEventRequest = AWSLogsPutLogEventsRequest()
logEventRequest?.logGroupName = LOG_GROUP
logEventRequest?.logStreamName = LOG_STREAM
let event = AWSLogsInputLogEvent()
event?.message = message
event?.timestamp = NSNumber(value: Int(Date().timeIntervalSince1970 * 1000))
logEventRequest?.logEvents = [event!]
logsClient.putLogEvents(logEventRequest!) { response, error in
if let error {
print("Error sending log event: \(error.localizedDescription)")
} else {
// Log events sent successfully.
}
}
}
AWSLogsPutLogEventsRequest
object and set the logGroupName
and logStreamName
properties to specify the destination log group and log stream in CloudWatch LogsAWSLogsInputLogEvent
object and set the message
property to the log message you want to send. Additionally, set the timestamp
property to the current timestamp in milliseconds.logEvents
property of the request object. In this case, it's wrapped in an array as the logEvents
property expects an array of log events.putLogEvents
method on the logsClient
object to initiate the request to send the log event to CloudWatch Logs.You can call this createLogEvent
function whenever you need to send a log event to CloudWatch Logs. Just provide the log message as a parameter to the function.
Now, Let’s create a custom class of DDAbstractLogger
for AWS logging.
class DDAWSLogger: DDAbstractLogger {
override init() {
createLogGroup()
createLogStream()
}
override func log(message logMessage: DDLogMessage) {
createLogEvent(message: logMessage.message)
}
}
We have called the createLogEvent
inside the log method, which will add a log to the cloud automatically whenever we get any log from the app.
Looking very simple, isn’t it?
Now, we just need to add this logger class to the CocoaLumberjack logging system with DDLog
.
DDLog.add(DDAWSLogger()) // AWS logger
By following these steps, the project logs generated by CocoaLumberjack will be automatically captured and sent to the AWS cloud.
You can view and analyze these logs in the AWS dashboard.
CocoaLumberjack can direct logs to a file or use it as a framework that integrates with the Xcode console.
To initialize logging to files, use the following code:
let awsLogger: AWSDDFileLogger = AWSDDFileLogger() // File Logger
awsLogger.rollingFrequency = TimeInterval(60*60*24) // 24 hours
awsLogger.logFileManager.maximumNumberOfLogFiles = 7
AWSDDLog.add(awsLogger)
In this example, we have added awsLogger
to CocoaLumberjack's loggers using the AWSDDLog.add()
method.
This allows the logs generated by CocoaLumberjack's log methods (e.g., DDLogDebug
, DDLogInfo
, etc.) to be sent to AWS CloudWatch through the configured AWS logger.
By combining the AWS logger with CocoaLumberjack’s log methods, we can log events and messages using CocoaLumberjack’s familiar syntax while also sending those logs to AWS CloudWatch for centralized storage and analysis.
To initialize logging to your Xcode console, use the following code:
AWSDDLog.add(AWSDDTTYLogger.sharedInstance) // TTY = Xcode console
That’s it.
Logging application data and diagnostic information to the console or a file can be very useful when debugging problems both during development and production. Having a solid logging solution in place is therefore essential.
Along with many other developers, I have created custom logging solutions for many projects, but CocoaLumberjack is an ideal replacement and it has a lot more to offer.
Remember to refer to the official CocoaLumberjack documentation for more detailed information and customization options.
Happy Logging… Happy Coding !!! 🤖🍻
Whether you need...