Skip to content

Repository files navigation

Swift version Pod version SPM compatible Carthage compatible License Pod platforms

A lightweight, extensible network logging framework for iOS. Log every HTTP request and response with zero configuration changes to your networking code.

Table of Contents

Why XNLogger?

  • Zero-config interception -- works with URLSession, Alamofire, and AFNetworking out of the box, no code changes needed in your networking layer.
  • Memory-efficient -- logs are written to disk, not held in memory, preventing crashes from large binary payloads (images, videos, etc.).
  • Multiple simultaneous handlers -- log to Xcode console, file, Slack, a remote server, or your own custom handler -- all at once.
  • Modern Swift -- Swift 6 concurrency safe, AsyncStream observation, Combine publisher, and SwiftUI-first UI.
  • In-app debugging UI -- shake your device or press a keyboard shortcut to browse, search, and share network logs with a built-in inspector.

Screenshots

Network Log list Request details Response details
Log lists Request details Response details
Share Multimedia preview Mini view mode (PIP)
Share logs Multimedia preview Mini view mode

Quick Start

// 1. Start logging (in AppDelegate or @main App init)
#if DEBUG
XNLogger.shared.startLogging()
#endif

// 2. That's it! Shake your device or press Ctrl+X in the Simulator to view logs.

// Optional: Add a console handler to also print logs in Xcode console
let consoleHandler = XNConsoleLogHandler.create()
XNLogger.shared.addLogHandlers([consoleHandler])

For SwiftUI apps, attach the logger sheet to your root view:

@main
struct MyApp: App {
    init() {
        #if DEBUG
        XNLogger.shared.startLogging()
        XNUIManager.shared.startGesture = .none // Use SwiftUI shake instead of UIKit
        #endif
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
                .xnLoggerSheet() // Shows logger UI on device shake
        }
    }
}

Requirements

iOS 15.0 or later

Installation

CocoaPods

pod 'XNLogger'

To integrate only for debug configuration:

pod 'XNLogger', :configurations => ['Debug']

Swift Package Manager

Add XNLogger to your project via Xcode:

  1. Go to File > Add Package Dependencies
  2. Enter the repository URL: https://github.com/sunilsharma08/XNLogger.git
  3. Choose your version rules (e.g., "Up to Next Major" from 4.0.0)

Note: When using SPM, auto-start is not available. You must manually call XNLogger.shared.startLogging(). See Quick Start.

Carthage

github "https://github.com/sunilsharma08/XNLogger"

Manually

Drag the folder "XNLogger" with the source files into your project.

  • Remove file called "Info.plist" inside folder "XNLogger", you might get error due to this.
  • Go to file "XNLoader.m" and replace import statement from "XNLogger/XNLogger-Swift.h" to <Your-app-target-name>-Swift.h. For example your app target name is AwesomeApp, then import statement will be
#import "AwesomeApp-Swift.h"

For more details on how to bridge swift code in Objective-C file check this apple doc - Importing Swift into Objective-C

Debug-Only Integration

To ensure XNLogger is completely excluded from release builds:

CocoaPods:

pod 'XNLogger', :configurations => ['Debug']

SPM / Carthage / Manual -- wrap all XNLogger usage in #if DEBUG:

#if DEBUG
import XNLogger
#endif

Features

Core

  • Logs all network traffic automatically via URLSession interception
  • Works with Alamofire & AFNetworking without extra configuration
  • Memory-efficient disk-based logging to prevent crashes from large payloads
  • Swift 6 concurrency safe (Sendable conformance throughout)
  • Swift & Objective-C compatibility

UI

  • In-app log browser via shake gesture or Ctrl+X keyboard shortcut
  • Mini view mode (picture-in-picture) -- resizable and draggable
  • Share logs via email, AirDrop, or clipboard
  • Save log files to desktop when running on Simulator
  • SwiftUI support with .xnLoggerSheet() modifier and XNLoggerView()
  • iPhone and iPad support

Handlers

  • Built-in handlers: Console, File, Remote, Slack
  • Multiple handlers can run simultaneously
  • Custom handler support via XNLogHandler protocol
  • Per-handler filters -- each handler can have independent filter rules

Observation

  • AsyncStream-based log observation for Swift concurrency
  • Combine publisher for reactive log observation
  • Delegate-based callbacks (XNLoggerDelegate)

Filtering & Formatting

  • Filters by scheme (http, https), host (www.example.com), or substring match
  • Invert any filter to exclude instead of include
  • Filters can be added or removed dynamically at runtime
  • Log formatter to control exactly which fields are logged

Usage

Start / Stop Logging

With CocoaPods, logging starts automatically after integration. With SPM, Carthage, or manual integration, call startLogging() in your app launch code. Press Ctrl+X or shake the device/simulator to view logs in-app.

Start Logging manually

XNLogger.shared.startLogging()

Stop Logging

XNLogger.shared.stopLogging()

To use logger only for debug builds, wrap in preprocessor macros:

#if DEBUG
    XNLogger.shared.startLogging()
#endif

Viewing Logs

Show XNLogger UI

XNUIManager.shared.presentUI()

Hide XNLogger UI

XNUIManager.shared.dismissUI()

Clear logs

XNUIManager.shared.clearLogs()

Custom Keyboard Shortcut

By default, Ctrl+X toggles the logger UI in the Simulator. To use a different shortcut:

XNUIManager.shared.registerShortcutKey("d", modifierFlags: [.command])

SwiftUI Support

Shake-triggered logger sheet

Attach the .xnLoggerSheet() modifier to your root view. The logger UI will appear as a sheet when the device is shaken.

@main
struct MyApp: App {
    init() {
        XNLogger.shared.startLogging()
        // Disable UIKit shake gesture to avoid duplicate UI
        XNUIManager.shared.startGesture = .none
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
                .xnLoggerSheet()
        }
    }
}

Present logger manually

Use XNLoggerView() directly in a .sheet() or .fullScreenCover():

@State private var showLogger = false

var body: some View {
    Button("Show Logs") { showLogger = true }
        .sheet(isPresented: $showLogger) {
            XNLoggerView()
        }
}

Log Handlers

XNLogger ships with four built-in handlers. You can use any combination simultaneously.

Handler Class Purpose
Console XNConsoleLogHandler Prints logs to Xcode console
File XNFileLogHandler Writes logs to rotating files on disk
Remote XNRemoteLogHandler Sends logs via HTTP POST to a custom server
Slack XNSlackLogHandler Posts logs to a Slack channel via webhook

Console Handler

let consoleHandler = XNConsoleLogHandler.create()
XNLogger.shared.addLogHandlers([consoleHandler])

File Handler

let fileHandler = XNFileLogHandler.create() // Default file name: "XNNetworkLog"
// Or with a custom file name:
let fileHandler = XNFileLogHandler.create(fileName: "AppNetworkLogs")

// Configuration (optional)
fileHandler.maxFileSize = 1024   // Max file size in KB (default: 1024 = 1 MB)
fileHandler.maxFileCount = 4     // Max number of rotated log files (default: 4)

XNLogger.shared.addLogHandlers([fileHandler])

Remote Handler

Send logs to your own server. The handler appends log content to the HTTP body of the provided URLRequest under the key "xn-log-msg".

var request = URLRequest(url: URL(string: "https://your-server.com/logs")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")

let remoteHandler = XNRemoteLogHandler.create(urlRequest: request)
XNLogger.shared.addLogHandlers([remoteHandler])

Requests made by the Remote handler are excluded from XNLogger interception, so they will not appear in your logs.

Slack Handler

Send logs directly to a Slack channel using an Incoming Webhook URL.

let slackHandler = XNSlackLogHandler.create(
    webhookUrl: "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
)
XNLogger.shared.addLogHandlers([slackHandler])

Requests made by the Slack handler are excluded from XNLogger interception.

Custom Handler

Create your own handler by conforming to the XNLogHandler protocol. Subclassing XNBaseLogHandler is recommended as it provides filter management and the shouldLogRequest / shouldLogResponse convenience methods.

class MyCustomHandler: XNBaseLogHandler, XNLogHandler {

    static func create() -> MyCustomHandler {
        return MyCustomHandler()
    }

    func xnLogger(logRequest logData: XNLogData) {
        guard shouldLogRequest(logData: logData) else { return }
        // Handle request
        print("Request to: \(logData.urlRequest.url?.absoluteString ?? "")")
    }

    func xnLogger(logResponse logData: XNLogData) {
        guard shouldLogResponse(logData: logData) else { return }
        // Handle response
        let status = (logData.response as? HTTPURLResponse)?.statusCode ?? 0
        print("Response [\(status)]")
    }
}

XNLogger.shared.addLogHandlers([MyCustomHandler.create()])

Remove Handlers

XNLogger.shared.removeHandlers([consoleHandler])
// Or remove all:
XNLogger.shared.removeAllHandlers()

Filters

Filters control which network requests are logged. They can be applied at two levels:

  • Logger-level (XNLogger.shared.addFilters) -- requests that don't pass are completely ignored by all handlers and do not appear in the in-app UI.
  • Handler-level (handler.addFilters) -- requests are still recorded but only logged by handlers whose filters they pass. Filters on one handler do not affect other handlers.

Available Filters

Filter Class Matches
Scheme XNSchemeFilter(scheme:) URL scheme (http, https)
Host XNHostFilter(host:) URL host (www.example.com)
Contains XNContainsFilter(filterString:) Any substring in the full URL

Add filters to logger (universal)

let httpsOnly = XNSchemeFilter(scheme: "https")
XNLogger.shared.addFilters([httpsOnly])

Remove filters from logger

XNLogger.shared.removeFilters([httpsOnly])

Add filters to a specific handler

let host = XNHostFilter(host: "www.example.com")
consoleHandler.addFilters([host])

Remove filters from handler

consoleHandler.removeFilters([host])

Contains Filter

Filter by any substring in the URL:

let apiFilter = XNContainsFilter(filterString: "/api/v2/")
XNLogger.shared.addFilters([apiFilter])

Inverting Filters

Any filter can be inverted to exclude matching URLs instead of including them:

let excludeHTTP = XNSchemeFilter(scheme: "http", invert: true)
// Logs only HTTPS requests

Or set the property after creation:

let filter = XNSchemeFilter(scheme: "http")
filter.invert = true

Formatters

By default, the logger logs all information except binary data. These settings can be adjusted per requirement. The formatter controls what fields to log and what to skip.

Formatter class XNLogFormatter has the following properties:

public var showRequest: Bool = true // Hide or show requests log.
public var showResponse: Bool = true // Hide or show response log.
public var showReqstWithResp: Bool = false // Show request with response, useful when `showRequest` is disabled.
public var showCurlWithReqst: Bool = true // Show curl request with request log.
public var showCurlWithResp: Bool = true // Show curl request when url request is displayed with response.
public var prettyPrintJSON: Bool = true // Log pretty printed json data.
public var logUnreadableRespBody: Bool = false // Show binary data like image, video, etc in response.
public var logUnreadableReqstBody: Bool = false // Show binary data like image, video, etc in request body.
public var showReqstMetaInfo: [XNRequestMetaInfo] = XNRequestMetaInfo.allCases // Details to be displayed in request log portion.
public var showRespMetaInfo: [XNResponseMetaInfo] = XNResponseMetaInfo.allCases // Details to be displayed in response log portion.
public var showReqstMetaInfoWithResp: [XNRequestMetaInfo] = XNRequestMetaInfo.allCases // Details to display for request when display as response portion.

Observing Logs

AsyncStream

Use logStream to observe log events with Swift concurrency. Each caller gets an independent stream that auto-cancels when the Task is cancelled.

.task {
    for await event in XNLogger.shared.logStream {
        switch event {
        case .request(let logData):
            print("Request: \(logData.urlRequest.url?.absoluteString ?? "")")
        case .response(let logData):
            let status = (logData.response as? HTTPURLResponse)?.statusCode
            print("Response [\(status ?? 0)]: \(logData.urlRequest.url?.absoluteString ?? "")")
        }
    }
}

Combine

Use logPublisher to subscribe to log events reactively:

import Combine

var cancellables = Set<AnyCancellable>()

XNLogger.shared.logPublisher
    .sink { event in
        switch event {
        case .request(let logData):
            print("Request: \(logData.urlRequest.url?.absoluteString ?? "")")
        case .response(let logData):
            print("Response: \(logData.urlRequest.url?.absoluteString ?? "")")
        }
    }
    .store(in: &cancellables)

Delegate

For traditional callback-based observation, use XNLoggerDelegate:

class NetworkMonitor: NSObject, XNLoggerDelegate {

    func startMonitoring() {
        XNLogger.shared.delegate = self
    }

    func xnLogger(didStartRequest logData: XNLogData) {
        print("Started: \(logData.urlRequest.url?.absoluteString ?? "")")
    }

    func xnLogger(didReceiveResponse logData: XNLogData) {
        let status = (logData.response as? HTTPURLResponse)?.statusCode ?? 0
        print("Completed [\(status)]: \(logData.urlRequest.url?.absoluteString ?? "")")
    }
}

Note: The delegate is a weak property. Only one delegate can be active at a time. For multiple observers, use logStream or logPublisher instead.

Limitations

  1. Does not log background URLSession tasks.
  2. WKWebView URLs will not be logged.

Working on logging background tasks and WKWebView URLs without using any private API. These limitations may be removed in future releases.

Contributing

Feel free to raise a PR for any bug fixes, features, or enhancements. When you are done with changes, raise a PR to the develop branch.

Another way to contribute to the project is to send a detailed issue when you encounter a problem. In bug details please provide steps to reproduce and some other details like Swift version, URL (if possible), URLSession configuration, etc.

Support

GitHub stars

If you find XNLogger useful, please consider giving it a star on GitHub. It helps others discover the project and motivates continued development and maintenance.

License

XNLogger is available under the MIT license.

About

Powerful network debugging & logging framework for iOS - inspect HTTP traffic, requests, responses, headers & bodies right inside your app.

Topics

Resources

Stars

27 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages