Posts in open source

A Better Local and Remote Logging on iOS With JustLog

The original post is published on the JUST EAT tech blog at the following URL http://tech.just-eat.com/2017/01/18/a-better-local-and-remote-logging-on-ios-with-justlog/

JustLog Banner

In this blog post we introduce the solution for local and remote logging we developed for the Just Eat iOS app. It’s named JustLog and it’s available open source on Github at https://github.com/justeat/JustLog.

Overview

At Just Eat, logging and monitoring are fundamental parts of our job as engineers. Whether you are a back-end engineer or a front-end one, you’ll often find yourself in the situation where understanding how your software behaves in production is important, if not critical. The ELK stack for real-time logging has gained great adoption over recent years, mainly in the back-end world where multiple microservices often interact with each other.

In the mobile world, the common approach to investigating issues is gathering logs from devices or trying to reproduce the issue by following a sequence of reported steps. Mobile developers are mostly familiar with tools such as Google Analytics or Fabric.io but they are tracking systems, not fully fledged logging solutions.

We believe tracking is different in nature from logging and that mobile apps should take advantage of ELK too in order to take their monitoring and analysis to another level. Remote logging the right set of information could provide valuable information that would be difficult to gather otherwise, unveil unexpected behaviours and bugs, and even if the data was properly anonymized, identify the sequences of actions of singular users.

JustLog takes logging on iOS to the next level. It supports console, file and remote Logstash logging via TCP socket out of the box. You can also setup JustLog to use logz.io with no effort. JustLog relies on CocoaAsyncSocket and SwiftyBeaver, exposes a simple swifty API but it also plays just fine with Objective-C.

JustLog sets the focus on remote logging, but fully covers the basic needs of local console and file logging.

Usage

JustLog, is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod "JustLog"

Import it into your files like so:

// swift
import JustLog

// Objective-C
@import JustLog;

This logging system strongly relies on SwiftyBeaver. We decided to adopt SwiftyBeaver due to the following reasons:

  • good and extensible design
  • ability to upload logs to the cloud
  • macOS app to analyze logs

A log can be of one of 5 different types, to be used according to the specific need. A reasonable adopted convention on mobile could be the following:

  • 📣 verbose: Use to trace the code, trying to find one part of a function specifically, sort of debugging with extensive information.
  • 📝 debug: Information that is helpful to developers to diagnose an issue.
  • ℹ️ info: Generally useful information to log (service start/stop, configuration assumptions, etc). Info to always have available but usually don’t care about under normal circumstances. Out-of-the-box config level.
  • ⚠️ warning: Anything that can potentially cause application oddities but an automatic recovery is possible (such as retrying an operation, missing data, etc.)
  • ☠️ error: Any error which is fatal to the operation, but not the service or application (can’t open a required file, missing data, etc.). These errors will force user intervention. These are usually reserved for failed API calls, missing services, etc.

When using JustLog, the only object to interact with is the shared instance of the Logger class, which supports 3 destinations:

  • sync writing to Console (custom destination)
  • sync writing to File (custom destination)
  • async sending logs to Logstash (usually part of an ELK stack)

Following is a code sample to configure and setup the Logger. It should be done at app startup time, in the applicationDidFinishLaunchingWithOptions method in the AppDelegate.

let logger = Logger.shared

// file destination
logger.logFilename = "justeat-demo.log"

// logstash destination
logger.logstashHost = "my.logstash.endpoint.com"
logger.logstashPort = 3515
logger.logstashTimeout = 5
logger.logLogstashSocketActivity = true

// default info
logger.defaultUserInfo = ["app": "my iOS App",
                          "environment": "production",
                          "tenant": "UK",
                          "sessionID": someSessionID]
logger.setup()

The defaultUserInfo dictionary contains a set of basic information to add to every log.

The Logger class exposes 5 functions for the different types of logs. The only required parameter is the message, optional error and userInfo can be provided. Here are some examples of sending logs to JustLog:

Logger.shared.verbose("not so important")
Logger.shared.debug("something to debug")
Logger.shared.info("a nice information", userInfo: ["some key": "some extra info"])
Logger.shared.warning("oh no, that won’t be good", userInfo: ["some key": "some extra info"])
Logger.shared.error("ouch, an error did occur!", error: someError, userInfo: ["some key": "some extra info"])

It plays nicely with Objective-C too:

[Logger.shared debug_objc:@"some message"];
[Logger.shared info_objc:@"some message" userInfo:someUserInfo];
[Logger.shared error_objc:@"some message" error:someError];
[Logger.shared error_objc:@"some message" error:someError userInfo:someUserInfo];

The message is the only required argument for each log type, while userInfo and error are optional. The Logger unifies the information from message, error, error.userInfo, userInfo, defaultUserInfo and call-site info/metadata in a single dictionary with the following schema form of type [String : Any] (we call this ‘aggregated form’). E.g. in JSON representation:

{
  "message": ...,
  "userInfo": {
    "NSLocalizedDescription": ...,
    "error_domain": ...,
    "some key": ...,
    ...
  },  
  "metadata": {
    "file": ...,
    "function": ...,
    "line": ...,
    ...
  }
}

All destinations (console, file, logstash) are enabled by default but they can be disabled at configuration time like so:

logger.enableConsoleLogging = false
logger.enableFileLogging = false
logger.enableLogstashLogging = false

The above 5 logs are treated and showed differently on the each destination:

Console

The console prints only the message.

Console

File

On file we store all the log info in the ‘aggregated form’.

2016-12-24 12:31:02.734  📣 VERBOSE: {"metadata":{"file":"ViewController.swift","app_version":"1.0 (1)","version":"10.1","function":"verbose()","device":"x86_64","line":"15"},"userInfo":{"environment":"production","app":"my iOS App","log_type":"verbose","tenant":"UK"},"message":"not so important"}
2016-12-24 12:31:36.777  📝 DEBUG: {"metadata":{"file":"ViewController.swift","app_version":"1.0 (1)","version":"10.1","function":"debug()","device":"x86_64","line":"19"},"userInfo":{"environment":"production","app":"my iOS App","log_type":"debug","tenant":"UK"},"message":"something to debug"}
2016-12-24 12:31:37.368  ℹ️ INFO: {"metadata":{"file":"ViewController.swift","app_version":"1.0 (1)","version":"10.1","function":"info()","device":"x86_64","line":"23"},"userInfo":{"environment":"production","app":"my iOS App","log_type":"info","tenant":"UK","some key":"some extra info"},"message":"a nice information"}
2016-12-24 12:31:37.884  ⚠️ WARNING: {"metadata":{"file":"ViewController.swift","app_version":"1.0 (1)","version":"10.1","function":"warning()","device":"x86_64","line":"27"},"userInfo":{"environment":"production","app":"my iOS App","log_type":"warning","tenant":"UK","some key":"some extra info"},"message":"oh no, that won’t be good"}
2016-12-24 12:31:38.475  ☠️ ERROR: {"metadata":{"file":"ViewController.swift","app_version":"1.0 (1)","version":"10.1","function":"error()","device":"x86_64","line":"47"},"userInfo":{"error_code":1234,"environment":"production","error_domain":"com.just-eat.test","log_type":"error","some key":"some extra info","NSLocalizedDescription":"description","NSLocalizedRecoverySuggestion":"recovery suggestion","app":"my iOS App","tenant":"UK","NSLocalizedFailureReason":"error value"},"message":"ouch, an error did occur!"}

Logstash

Before sending a log to Logstash, the ‘aggregated form’ is flattened to a simpler `[String : Any] dictionary, easily understood by Logstash and handy to be displayed on Kibana. E.g. in JSON representation:

{
  "message": "ouch, an error did occur!",

  "environment": "production",
  "log_type": "error",
  "version": "10.1",
  "app": "iOS UK app",
  "tenant": "UK",
  "app_version": "1.0 (1)",
  "device": "x86_64",

  "file": "ViewController.swift",
  "function": "error()",
  "line": "47",

  "error_domain": "com.just-eat.test",
  "error_code": "1234",
  "NSLocalizedDescription": "description",
  "NSLocalizedFailureReason": "error value",
  "NSLocalizedRecoverySuggestion": "recovery suggestion"
}

Which would be shown in Kibana as follows:

Kibana

A note on Logstash destination

The logstash destination is configured via properties exposed by the Logger. E.g.:

let logger = Logger.shared
logger.logstashHost = "my.logstash.endpoint.com"
logger.logstashPort = 3515
logger.logstashTimeout = 5
logger.logLogstashSocketActivity = true

When the logLogstashSocketActivity is set to true, socket activity is printed to the console:

Socket Activity

This destination is the only asynchronous destination that comes with JustLog. This means that logs to Logstash are batched and sent at some point in future when the timer fires. The logstashTimeout property can be set to the number of seconds for the dispatch. In some cases, it might be important to dispatch the logs immediately after an event occurs like so:

Logger.shared.forceSend()

or, more generally, in the applicationDidEnterBackground and applicationWillTerminate methods in the AppDelegate like so:

func applicationDidEnterBackground(_ application: UIApplication) {
    forceSendLogs(application)
}

func applicationWillTerminate(_ application: UIApplication) {
    forceSendLogs(application)
}

private func forceSendLogs(_ application: UIApplication) {

    var identifier: UIBackgroundTaskIdentifier = 0

    identifier = application.beginBackgroundTask(expirationHandler: {
        application.endBackgroundTask(identifier)
        identifier = UIBackgroundTaskInvalid
    })

    Logger.shared.forceSend { completionHandler in
        application.endBackgroundTask(identifier)
        identifier = UIBackgroundTaskInvalid
    }
}

Sending logs to logz.io

JustLog supports sending logs to logz.io.

At the time of writing, logz.io uses the following host and port (please refer to the official documentation):

logger.logstashHost = "listener.logz.io"
logger.logstashPort = 5052

When configuring the Logger (before calling setup()), simply set the token like so:

logger.logzioToken = <logzio_token>

Conclusion

JustLog aims to be an easy-to-use working solution with minimal setup. It covers the most basic logging needs (console and file logging) via the great foundations given by SwiftBeaver, but also provides an advanced remote logging solution for Logstash (which is usually paired with Elasticsearch and Kibana in an ELK stack). JustLog integrates with logz.io, one of the most widely used ELK SaaS, placing itself as the only solution in the market (at the time of writing) to leverage such stack on iOS.

We hope this library will ease the process of setting up the logging for your team and help you find solutions to the issues you didn’t know you had.

written in elk, ios, logging, logstash, open source

From the Eyes of an iOS Dev at JUST EAT

It has been almost 6 months since my last blog post. Things have changed quite a lot since then. Six months ago I was still excited about my travel to San Francisco for the WWDC 2014, my girlfriend still had to move from Italy to London with me and definitely I wasn’t planning to switch job again any time soon.

Overture

I’ve been attracted by JUST EAT as a company since March 2014 but at that time it was too early for me to consider to change job. I met Ben Chester (the tech lead of the iOS team) when he gave a talk at Badoo offices (when I was still working there) and that evening he blew my mind. Later, I had a few chances to have a chat with the passionated guy he is and I immediately thought “Damn! I want to work with this guy, with brilliant guys like him and I want to work at JUST EAT!”.

Since then, I heard people talking extremely good about JUST EAT as a job place because of the values, the work environment, the company culture and the engineeristic approach to things. Every time I started with “Do you know JUST EAT as a company?” the answer was something like “Oh yeah! They are freaking cool! I have a friend working there, they do amazing stuff and he’s very very happy!”. They definitely were all good signs. Signs I decided not to underestimate anymore these days.

Another good sign was also the exposure and lots of information that the company promotes online with its tech blog giving a good insight of the technologies used, the people and teams working there and a good description of the Engineering. Benefits are also compelling.

JUST EAT offices hosted NSLondon a few times. Meetups, you know, are the perfect occasions to reach out the developers' community. I noticed too many cool companies failing at this.

After months of interest about JUST EAT and thoughts spinning in my head, I said to myself “let’s see if I have what it takes”. I decided to apply for the Senior iOS role in later October 2014 kicking off the process taking the test task. I joined the Consumer iOS app team at the begin of 2015 and after 2 months of excitement I’m summarizing some thoughts here.

https://s3.amazonaws.com/albertodebortoli.github.com/images/justeat/justeat_wall.jpg" width="720" height="275">

written in github, ios, just eat, london, open source, work Read on →

Working on Tasks With an Eye on Open Source Contributions

Have you ever realized that developers are never happy with the legacy code?

The definition of “legacy code” may vary:

  • code inherited from the previous developers of your company
  • code that doesn’t have test suites
  • code that is older that 10 minutes (…)

We all rarely find good code when joining a company, weird uh? Some reasons why good code is so hard to find can be:

  • the developer that worked on the code was good but couldn’t care less about doing things properly
  • the developer that worked on the code was simply unexperienced and created bizzare things
  • the developer that worked on the code was terrible at architecture design
  • too many developers worked on the same code without understanding what was already been done
  • code was not developed using the black box approach and without reusability in mind

All points but last are summarized here:

https://s3.amazonaws.com/albertodebortoli.github.com/images/eye-on-open-source/developers.jpg">

written in github, ios, objective-c, open source, work Read on →