Skip to content

PerseusRealDeal/ConsolePerseusLogger

Repository files navigation

ConsolePerseusLogger — Xcode 14.2+

Actions Status Style Version Platforms Xcode 14.2 Swift 5.7 License

[TYPE] [DATE] [TIME] [PID:TID] message, file: #, line: #

This is the great home-made product in Swift. Light-weight logging lover.

1: Log to the console.
2: Log to macOS Console.
3: Log to custom output.
4: Collect log messages.
5: Delegate log message.

CPL is a single author and personale solution developed in P2P relationship paradigm.

Integration Capabilities

Standalone Swift Package Manager compatible

Our Terms

CPL stands for Console Perseus Logger.
PGK stands for Perseus Geo Kit.
PDM stands for Perseus Dark Mode.
P2P stands for Person-to-Person.
A3 stands for Apple Apps Approbation.
T3 stands for The Technological Tree.

CPL in Use

In approbation: iOS app macOS app
In business: The Dark Moon PerseusGeoKit PerseusDarkMode Convertor mov2gif

For details: Approbation and A3 Environment / CHANGELOG / Xcode Playground

Contents

In brief

USE LOGGER LIKE A VARIABLE ANYWHERE YOU WANT

Image

Build requirements

Tip

As the single source code CPLStar.swift CPL with minimum changes can be used even in Xcode 10.1, just remove all statements starting with if #available(iOS 14.0, macOS 11.0, *).

Third-party software

Type Name License
Style SwiftLint / v0.57.0 for Monterey+ MIT
Script SwiftLint Shell Script to run SwiftLint MIT
Action mxcl/xcodebuild@v3 Unlicense
Action cirruslabs/swiftlint-action@v1 MIT

Installation

Standalone: Place CPLStar.swift into your project directly

log.message("[\(type(of: self))].\(#function)")

Swift Package Manager: https://github.com/perseusrealdeal/ConsolePerseusLogger

Exact Version is strongly recommended.

import ConsolePerseusLogger

log.message("[\(type(of: self))].\(#function)")

Note

If output is .consoleapp and Environment Variable OS_ACTIVITY_MODE in disable log messaging will be restricted for Xcode console, but only.

Image

Usage

Log to the console

import ConsolePerseusLogger

log.message("[\(type(of: self))].\(#function)")

Image

Log to macOS Console

Note

To pass messages to Console.app CPL employs Logger structure (from iOS 14.0, macOS 11.0) and OSLog.

Case 1: Redirect all messages to .consoleapp

log.output = .consoleapp
log.message("[\(type(of: self))].\(#function)")

Image

Case 2: Redirect the message to .consoleapp

log.message("[\(type(of: self))].\(#function)", .debug, .consoleapp)

Tip

Set custom titles for Console.app Subsystem and Category

log.logObject = ("MyApp", "MyLover") // Customs for Console.app Subsystem and Category.

Custom log

For any specific combination of the log message details.

import ConsolePerseusLogger

func customPrint(_ instance: LogMessage) {

    let time = "[\(instance.localTime.date)] [\(instance.localTime.time)]"
    let utc = instance.localTime.timeUTC

    let id = "[\(instance.owner.pid):\(instance.owner.tid)]"
    let dirs = "file: \(instance.fileline.fileName), line: \(instance.fileline.line)"

    print("[MYLOG] \(instance.text) \(id) \(time) UTC: \(utc), \(dirs)")
}

log.customActionOnMessage = customPrint(_:)

log.format = .textonly
log.output = .custom

log.message("The app's start point...", .info)

Image

Debugging SwiftUI

Case 1: As Is

Image(systemName: "globe")
    .onAppear {
        log.message("This is the debug output.")
    }

Case 2: Wrapper

Extend View with the method message(_:_:_:_:_:):

extension View {
    func message(_ text: @autoclosure () -> String,
                 _ type: PerseusLogger.Level = .debug,
                 _ oput: PerseusLogger.Output = PerseusLogger.output,
                 _ user: PerseusLogger.User = .operative,
                 _ file: StaticString = #file,
                 _ line: UInt = #line) -> Self {

        log.message(text(), type, oput, user, file, line)

        return self
    }
}

Then use message as a view modifier to output a message:

VStack {
   ForEach(colors, id: \.self) { color in
      Circle()
         .foregroundColor(color)
         .message("\(color)")
   }
}

Image

Log level and message types

CPL applies the most common log types for indicating information category.

Level Message Type Description
5 DEBUG Debugging only
4 INFO Helpful, but not essential
3 NOTICE Might result in a failure
2 ERROR Errors seen during the code execution
1 FAULT Faults and bugs in the code

Important

NOTICE in use with end-user notifications as a recommended type, it's a default and almost neutral level.

Also, CPL considers Message Type to filter, look how it works:

Image

Configuration

Default settings

Default values of CPL options depend on DEBUG/RELEASE.

Option Default in DEBUG Default in RELEASE
tuned .on .off
output .standard .consoleapp
level .debug .notice

Other CPL options are the same for DEBUG/RELEASE by default.

Option Default in DEBUG Default in RELEASE
subsecond .nanosecond .nanosecond
tidnumber .hexadecimal .hexadecimal
format .short .short
linemode .singleLine .singleLine
marks true true
time false false
owner false false
directives false false
logObject ("Perseus", "Lover") ("Perseus", "Lover")

Special option goes kinda lifehack. Matter only if Simulator.

Option Default in DEBUG Default in RELEASE
debugIsInfo true true

Load settings with JSON config

Each option can be reseted in run time with json config except option turned.

Case 1: Using predefined json profile

import ConsolePerseusLogger

let isReseted = log.loadConfig(.debugRoutine)
let result = isReseted ? "CPL current options loaded." : "Failed to load options!"

log.message(result)

Case 2: Using custom profile (URL required)

import ConsolePerseusLogger

var result = ""

if let path = Bundle.main.url(forResource: "CPLConfig", withExtension: "json") {
    let isLoaded = log.loadConfig(path)
    result = isLoaded ? "Options successfully loaded." : "Failed to load options!"
} else {
    result = "Failed to create URL!"
}

log.message(result)

Image

SPM package

In SPM package CPL can be used as standalone, just place CPLStar.swift into your project directly.

Important

Statement typealias log = PerseusLogger in SPM package should be not public.

Important

Statement protocol PerseusDelegatedMessage in SPM package should be not public, also.

//
//  main.swift
//

import ConsolePerseusLogger

import class PackageA.PerseusLogger
import class PackageB.PerseusLogger

typealias logA = PackageA.PerseusLogger
typealias logB = PackageB.PerseusLogger

// MARK: - Subloggers

logA.turned = .off
logB.turned = .off

// MARK: - The Logger

log.message("The app's start point...", .info)

macOS Console and Simulator

Note

CPL employs Logger structure (from iOS 14.0, macOS 11.0) and OSLog to pass messages to macOS Console.

Just a matter of fact that Console.app doesn't show any DEBUG message from any app running on Simulator (even if "Include Debug Messages" tapped in Console.app).

Console Perseus Logger running on Simulator doesn't pass DEBUG message to Console.app, instead it passes INFO message with text of DEBUG message by default, so, a passed message being INFO looks like a DEBUG and it works perfactly well.

If for some reasons CPL must pass DEBUG as a DEBUG message the option should take false log.debugIsInfo = false, but Console.app will not show DEBUG messages.

#if targetEnvironment(simulator)
    log.debugIsInfo = false
#endif

Collecting logs

PerseusLogger.Report and KVO can be used to view last log messages.

import Foundation

let report = PerseusLogger.Report()
var observation: NSKeyValueObservation?

observation = report.observe(\.lastMessage, options: .new) { _, change in
    // Refresh code
}

log.customActionOnMessage = report.report(_:)

Step 1: Create a report

Step 2: Create an observer for the last message to refresh on change

Step 3: log.customActionOnMessage = report.report(_:)

Note

Override method report(_:) of PerseusLogger.Report to meet expectations with specifics.

Warning

You must be careful to avoid infinite recursion when you try to add a log statement to KVO block definition.

observation = report.observe(\.lastMessage, options: .new) { _, change in
    
    // Infinite recursion case, fatal error.
    log.message("[\(type(of: self))].\(#function)") // Leads to stack overflow.
    
    // Refresh code
}

Image

Delegating logs

End-user message statement

A log message can be used for easy creating end-user notifications in a way like this:

log.message("Notification...", .notice, .custom, .enduser)

Important

  1. End-user message egnores log.turned = .off, but log.level is still matter.
  2. Message type .notice is recommended, it's a default and almost neutral level.
  3. Output .custom is in use to process end-user messages.

Image

To delegate with reporting

Step 1: Find a type to implement PerseusDelegatedMessage protocol

Step 2: Create a report of PerseusLogger.Report

Step 3: Set the delegate report.messageDelegate to the PerseusDelegatedMessage one

Step 4: log.customActionOnMessage = report.report(_:)

Image

To delegate without reporting

import ConsolePerseusLogger

class MyEndUserMessageClass: PerseusDelegatedMessage {
    var message: String = "" {
        didSet {
            didSetEndUserMessage()
        }
    }

    private func didSetEndUserMessage() {
        log.message("[\(type(of: self))].\(#function): \(message)")
    }
}

func customPrint(_ instance: LogMessage) {

    if instance.user == .enduser {
        delegate?.message = instance.text
        return
    }

    print(instance.getMessage() + " customed!")
}

log.customActionOnMessage = customPrint(_:)

let delegate: PerseusDelegatedMessage? = MyEndUserMessageClass()
let greeting = "Hello"

log.message("\(greeting) 1", .notice) // .standard
log.message("\(greeting) 2", .notice, .custom) // .custom
log.message("\(greeting) 3", .notice, .custom, .enduser) // .custom delegated to .enduser

Image

Points taken into account

License

License: MIT

Copyright © 7531 - 7534 Mikhail A. Zhigulin of Novosibirsk
Copyright © 7531 - 7534 PerseusRealDeal

  • The year starts from the creation of the world according to a Slavic calendar.
  • September, the 1st of Slavic year. It means that "Sep 01, 2025" is the beginning of 7534.

Other required license notices

© 2025 The SwiftLint Contributors for SwiftLint
© GitHub for GitHub Action cirruslabs/swiftlint-action@v1
© 2021 Alexandre Colucci, geteimy.com for Shell Script SucceedsPostAction.sh

LICENSE for details.

Credits

Balance and Control kept by Mikhail Zhigulin
Source Code written by Mikhail Zhigulin
Documentation prepared by Mikhail Zhigulin
Product Approbation tested by Mikhail Zhigulin

Author

© Mikhail A. Zhigulin of Novosibirsk.