Skip to content

Commit

Permalink
Merge branch 'develop' into issues/62-deadlock-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
kober32 committed Jul 6, 2024
2 parents e3e8ee9 + 9b58f60 commit 1d8a976
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 55 deletions.
30 changes: 18 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,27 +337,33 @@ The default value is always `en`. With other languages, we use values compliant

## Logging

For logging purposes `WPNLogger` that prints to the console is used.

<!-- begin box info -->
Note that logging to the console is available only when the library is compiled with the `DEBUG` or `WPN_ENABLE_LOGGING` Swift compile condition.
<!-- end -->
You can set up logging for the library using the `WPNLogger` class.

### Verbosity Level

You can limit the amount of logged information via `verboseLevel` property.
You can limit the amount of logged information via the `verboseLevel` property.

| Level | Description |
| --- | --- |
| `off` | Silences all messages. |
| `errors` | Only errors will be printed to the debug console. |
| `warnings` _(default)_ | Errors and warnings will be printed to the debug console. |
| `all` | All messages will be printed to the debug console. |
| Level | Description |
| ---------------------- | ------------------------------------------------- |
| `off` | Silences all messages. |
| `errors` | Only errors will be logged. |
| `warnings` _(default)_ | Errors and warnings will be logged. |
| `info` | Error, warning and info messages will be logged. |
| `all` | All messages will be logged. |

### Character limit

To prevent huge logs from being printed out, there is a default limit of 12,000 characters per log in place. You can change this via `WPNLogger.characterLimit`.

### HTTP traffic logs

- You can turn on or off logging of the requests and responses with the `WPNLogger.logHttpTraffic` property.
- You can filter which headers will be logged with the `WPNLogger.httpHeadersToSkip` property.

### Logger Delegate

In case you want to process logs on your own (for example log into a file or some cloud service), you can set `WPNLogger.delegate`.

<!-- begin remove -->
## Web Documentation

Expand Down
33 changes: 16 additions & 17 deletions Sources/WultraPowerauthNetworking/WPNHttpClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,27 @@ class WPNHttpClient: NSObject, URLSessionDelegate {

private extension URLRequest {
func printToConsole() {
D.print("WPNHttpClient Request")
D.print("- URL: POST - \(url?.absoluteString ?? "no URL")")
D.print("- Headers: \(allHTTPHeaderFields?.betterDescription ?? "no headers")")
D.print("- Body: \(httpBody?.utf8string ?? "empty body")")
if D.logHttpTraffic {
D.info("WPNHttpClient Request")
D.info("- URL: POST - \(url?.absoluteString ?? "no URL")")
D.info("- Headers: \(D.httpHeadersToSkip.filterHeaders(headers: allHTTPHeaderFields))")
D.debug("- Body: \(httpBody?.utf8string ?? "empty body")")
}
}
}

private extension HTTPURLResponse {
func printToConsole(withData data: Data?, andError error: Error?) {
D.print("WPNHttpClient Response")
D.print("- URL: POST - \(url?.absoluteString ?? "no URL")")
D.print("- Status code: \(statusCode)")
D.print("- Headers: \(allHeaderFields.betterDescription)")
D.print("- Body: \(data?.utf8string ?? "empty body")")
if let error = error {
D.print("- Error: \(error.localizedDescription)")
if D.logHttpTraffic {
D.info("WPNHttpClient Response")
D.info("- URL: POST - \(url?.absoluteString ?? "no URL")")
D.info("- Status code: \(statusCode)")
D.info("- Headers: \(D.httpHeadersToSkip.filterHeaders(headers: Dictionary(uniqueKeysWithValues: allHeaderFields.map { ($0.key.description, "\($0.value)") })))")
D.debug("- Body: \(data?.utf8string ?? "empty body")")

if let error = error {
D.error("- Error: \(error.localizedDescription)")
}
}
}
}
Expand All @@ -105,9 +110,3 @@ private extension Data {
return String(bytes: self, encoding: .utf8)
}
}

private extension Dictionary where Key: CustomStringConvertible, Value: Any {
var betterDescription: String {
return map({($0.key.description, $0.value)}).description
}
}
209 changes: 183 additions & 26 deletions Sources/WultraPowerauthNetworking/WPNLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,52 +16,133 @@

import Foundation

/// WPNLogger provides simple logging facility available for DEBUG build of the library.
/// Level of the log
public enum WPNLogLevel {
/// Debug logs. Might contain sensitive data like body of the request etc.
/// You should only use this level during development.
case debug
/// Regular library logic logs
case info
/// Non-critical warning
case warning
/// Error happened
case error

fileprivate var minVerboseLevel: WPNLogger.VerboseLevel {
return switch self {
case .debug: .debug
case .info: .info
case .warning: .warnings
case .error: .errors
}
}

fileprivate var logName: String {
return switch self {
case .debug: "DEBUG"
case .info: "INFO"
case .warning: "WARNING"
case .error: "ERROR"
}
}
}

/// Delegate that can further process logs from the library
public protocol WPNLoggerDelegate: AnyObject {

/// If the delegate should follow selected verbosity level.
///
/// When set to true, then (for example) if `errors` is selected as a `verboseLevel`, only `error` logLevel will be called.
/// When set to false, all methods might be called no matter the selected `verboseLevel`.
var wpnFollowVerboseLevel: Bool { get }

/// Log was recorded
/// - Parameters:
/// - message: Message of the log
/// - logLevel: Log level
func wpnLog(message: String, logLevel: WPNLogLevel)
}

/// WPNLogger provides simple logging facility.
public class WPNLogger {

/// Defines verbose level for this simple debugging facility.
/// Verbose level of the logger.
public enum VerboseLevel: Int {
/// Silences all messages.
case off = 0
/// Only errors will be printed to the debug console.
/// Only errors will be printed to the system console.
case errors = 1
/// Errors and warnings will be printed to the debug console.
/// Errors and warnings will be printed to the system console.
case warnings = 2
/// All messages will be printed to the debug console.
case all = 3
/// Error ,warning and info messages will be printed to the system console.
case info = 3
/// All messages will be printed to the system console - including debug messages
case debug = 4
}

/// Current verbose level. Note that value is ignored for non-DEBUG builds.
/// Logger delegate
public static weak var delegate: WPNLoggerDelegate?

/// Current verbose level. `warnings` by default
public static var verboseLevel: VerboseLevel = .warnings

/// Character limit for single log message. Default is 12 000. Unlimited when nil
/// If HTTP traffic should be reported by this logger. `true` by default
///
/// You can use this option to stop log from the HTTP traffic when you setup your own logging logic
/// via the `responseDelegate` and `requestDelegate` in the `WPNNetworkingService`.
public static var logHttpTraffic = true

/// Headers that won't be logged.
///
/// Default headers to skip are:
/// ```
/// "accept-language", "content-type", "content-length",
/// "accept-language", "transfer-encoding", "date",
/// "server", "user-agent", "connection", "x-content-type-options",
/// "x-xss-protection", "cache-control", "pragma", "expires",
/// "x-frame-options", "vary"
/// ```
public static let httpHeadersToSkip = HeaderBlockList()

/// Character limit for single log message. Default is `12 000`. Unlimited when nil
public static var characterLimit: Int? = 12_000

/// Prints simple message to the debug console.
static func print(_ message: @autoclosure () -> String) {
#if DEBUG || WPN_ENABLE_LOGGING
if verboseLevel == .all {
Swift.print("[WPN] \(message().limit(characterLimit))")
}
#endif
/// Prints simple message to the system console.
static func debug(_ message: @autoclosure () -> String) {
log(message(), level: .debug)
}

/// Prints simple message to the system console.
static func info(_ message: @autoclosure () -> String) {
log(message(), level: .info)
}

/// Prints warning message to the debug console.
/// Prints warning message to the system console.
static func warning(_ message: @autoclosure () -> String) {
#if DEBUG || WPN_ENABLE_LOGGING
if verboseLevel.rawValue >= VerboseLevel.warnings.rawValue {
Swift.print("[WPN] WARNING: \(message().limit(characterLimit))")
}
#endif
log(message(), level: .warning)
}

/// Prints error message to the debug console.
/// Prints error message to the system console.
static func error(_ message: @autoclosure () -> String) {
#if DEBUG || WPN_ENABLE_LOGGING
if verboseLevel != .off {
Swift.print("[WPN] ERROR: \(message().limit(characterLimit))")
log(message(), level: .error)
}

private static func log(_ message: @autoclosure () -> String, level: WPNLogLevel) {
let levelAllowed = level.minVerboseLevel.rawValue <= verboseLevel.rawValue
let forceReport = delegate?.wpnFollowVerboseLevel == false
guard levelAllowed || forceReport else {
// not logging
return
}

let msg = message().limit(characterLimit)

if levelAllowed {
print("[WPN:\(level.logName)] \(msg)")
}
if levelAllowed || forceReport {
delegate?.wpnLog(message: msg, logLevel: level)
}
#endif
}

#if DEBUG
Expand All @@ -87,6 +168,82 @@ public class WPNLogger {
#endif
}

/// Headers to skip when logging.
///
/// Note that all headers are transformed to lowercase variant when added.
///
/// Default headers to skip are:
/// ```
/// "accept-language", "content-type", "content-length", "accept-language", "transfer-encoding", "date", "server", "user-agent",
/// "connection", "x-content-type-options", "x-xss-protection", "cache-control", "pragma", "expires", "x-frame-options", "vary"
/// ```
///
public class HeaderBlockList {

private var headersToSkp = [
"accept-language", "content-type", "content-length", "accept-language", "transfer-encoding", "date", "server", "user-agent",
"connection", "x-content-type-options", "x-xss-protection", "cache-control", "pragma", "expires", "x-frame-options", "vary"
]

/// Adds element to the block list.
/// - Parameter element: HTTP header key to block.
public func add(element: String) {
headersToSkp.append(element.lowercased())
}

/// Adds elements to the block list.
/// - Parameter element: HTTP header keys to block.
public func add(elements: [String]) {
headersToSkp.append(contentsOf: elements.map { $0.lowercased() })
}

/// Removes element from the block list.
/// - Parameter element: HTTP header key to remove.
public func remove(element: String) {
headersToSkp.removeAll { $0 == element.lowercased() }
}

/// Removes elements from the block list.
/// - Parameter element: HTTP header keys to remove.
public func removeAll(elements: [String]) {
elements.map { $0.lowercased() }.forEach {
if let idx = headersToSkp.firstIndex(of: $0) {
headersToSkp.remove(at: idx)
}
}
}

/// Remove all
public func removeAll() {
headersToSkp.removeAll()
}

/// Returns array of headers to skip
/// - Returns: Headers to skip
public func headersToSkip() -> [String] {
return Array(headersToSkp)
}

func filterHeaders(headers: [String: String]?) -> String {

guard let headers else {
return "no headers"
}

var result = ""
var skipped = 0

for header in headers {
if headersToSkp.contains(where: { $0 == header.key.lowercased() }) {
skipped += 1
} else {
result += "\n - \(header.key): \(header.value)"
}
}
return "\(skipped) filtered out" + result
}
}

private extension String {
func limit(_ characterLimit: Int?) -> String {
guard let cl = characterLimit else {
Expand Down
3 changes: 3 additions & 0 deletions Sources/WultraPowerauthNetworking/WPNNetworkingService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ public class WPNNetworkingService {
self.responseDelegate?.responseReceived(from: request.url, statusCode: urlResponse?.statusCode, body: receivedData)
resp = envelope
case .encrypted(let envelope, let decryptedData):
if D.logHttpTraffic {
D.debug("Decrypted response from \(request.url.absoluteString):\n\(String(decoding: decryptedData, as: UTF8.self) ?? "empty")")
}
self.responseDelegate?.encryptedResponseReceived(from: request.url, statusCode: urlResponse?.statusCode, body: receivedData, decrypted: decryptedData)
resp = envelope
case .failed:
Expand Down

0 comments on commit 1d8a976

Please sign in to comment.