Skip to content

Commit e4e32fd

Browse files
authored
Merge pull request #17 from Mordil/14-logging
Add initial `swift-log` support for most paths
2 parents b6f53cf + 73d5930 commit e4e32fd

File tree

6 files changed

+81
-12
lines changed

6 files changed

+81
-12
lines changed

Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ let package = Package(
88
.library(name: "NIORedis", targets: ["NIORedis"])
99
],
1010
dependencies: [
11+
.package(url: "https://github.com/apple/swift-log.git", .branch("master")),
1112
.package(url: "https://github.com/apple/swift-nio.git", .branch("master"))
1213
],
1314
targets: [
14-
.target(name: "NIORedis", dependencies: ["NIO"]),
15+
.target(name: "NIORedis", dependencies: ["NIO", "Logging"]),
1516
.testTarget(name: "NIORedisTests", dependencies: ["NIORedis", "NIO"])
1617
]
1718
)

Sources/NIORedis/ChannelHandlers/RESPEncoder+M2BE.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,15 @@ extension RESPEncoder: MessageToByteEncoder {
77
/// See `RESPEncoder.encode(data:out:)`
88
public func encode(data: RESPValue, out: inout ByteBuffer) throws {
99
encode(data, into: &out)
10+
11+
logger.debug("Encoded \(data) to \(getPrintableString(for: &out))")
12+
}
13+
14+
// used only for debugging purposes where we build a formatted string for the encoded bytes
15+
private func getPrintableString(for buffer: inout ByteBuffer) -> String {
16+
return String(describing: buffer.getString(at: 0, length: buffer.readableBytes))
17+
.dropFirst(9)
18+
.dropLast()
19+
.description
1020
}
1121
}

Sources/NIORedis/ChannelHandlers/RedisCommandHandler.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Foundation
2+
import Logging
23
import NIO
34

45
/// A context for `RedisCommandHandler` to operate within.
@@ -18,9 +19,17 @@ public struct RedisCommandContext {
1819
open class RedisCommandHandler {
1920
/// Queue of promises waiting to receive a response value from a sent command.
2021
private var commandResponseQueue: [EventLoopPromise<RESPValue>]
22+
private var logger: Logger
2123

22-
public init() {
24+
deinit {
25+
guard commandResponseQueue.count > 0 else { return }
26+
logger.warning("Command handler deinit when queue is not empty. Current size: \(commandResponseQueue.count)")
27+
}
28+
29+
public init(logger: Logger = Logger(label: "NIORedis.CommandHandler")) {
2330
self.commandResponseQueue = []
31+
self.logger = logger
32+
self.logger[metadataKey: "CommandHandler"] = "\(UUID())"
2433
}
2534
}
2635

@@ -49,7 +58,9 @@ extension RedisCommandHandler: ChannelInboundHandler {
4958
let value = unwrapInboundIn(data)
5059

5160
guard let leadPromise = commandResponseQueue.last else {
52-
return assertionFailure("Read triggered with an empty promise queue! Ignoring: \(value)")
61+
assertionFailure("Read triggered with an empty promise queue! Ignoring: \(value)")
62+
logger.emergency("Read triggered with no promise waiting in the queue!")
63+
return
5364
}
5465

5566
let popped = commandResponseQueue.popLast()

Sources/NIORedis/RESP/RESPEncoder.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
import Logging
2+
13
/// Encodes `RedisValue` into a raw `ByteBuffer`, formatted according to the Redis Serialization Protocol (RESP).
24
///
35
/// See: [https://redis.io/topics/protocol](https://redis.io/topics/protocol)
46
public final class RESPEncoder {
5-
public init() { }
7+
let logger: Logger
8+
9+
public init(logger: Logger = Logger(label: "NIORedis.RESPEncoder")) {
10+
self.logger = logger
11+
}
612

713
/// Encodes the `RedisValue` to bytes, following the RESP specification.
814
///

Sources/NIORedis/RedisConnection.swift

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import Logging
2+
import Foundation
13
import NIO
24
import NIOConcurrencyHelpers
35

@@ -51,6 +53,8 @@ extension RedisConnection {
5153
public var eventLoop: EventLoop { return self.channel.eventLoop }
5254
}
5355

56+
private let loggingKeyID = "RedisConnection"
57+
5458
/// A basic `RedisConnection`.
5559
public final class NIORedisConnection: RedisConnection {
5660
/// See `RedisConnection.channel`.
@@ -60,36 +64,51 @@ public final class NIORedisConnection: RedisConnection {
6064
public var isClosed: Bool { return _isClosed.load() }
6165
private var _isClosed = Atomic<Bool>(value: false)
6266

67+
private var logger: Logger
68+
6369
deinit { assert(_isClosed.load(), "Redis connection was not properly shut down!") }
6470

6571
/// Creates a new connection on the provided channel.
6672
/// - Note: This connection will take ownership of the `Channel` object.
6773
/// - Important: Call `close()` before deinitializing to properly cleanup resources.
68-
public init(channel: Channel) {
74+
public init(channel: Channel, logger: Logger = Logger(label: "NIORedis.Connection")) {
6975
self.channel = channel
76+
self.logger = logger
77+
78+
self.logger[metadataKey: loggingKeyID] = "\(UUID())"
79+
self.logger.debug("Connection created.")
7080
}
7181

7282
/// See `RedisConnection.close()`.
7383
@discardableResult
7484
public func close() -> EventLoopFuture<Void> {
75-
guard !_isClosed.exchange(with: true) else { return channel.eventLoop.makeSucceededFuture(()) }
85+
guard !_isClosed.exchange(with: true) else {
86+
logger.notice("Connection received more than one close() request.")
87+
return channel.eventLoop.makeSucceededFuture(())
88+
}
7689

7790
return send(command: "QUIT")
7891
.flatMap { _ in
7992
let promise = self.channel.eventLoop.makePromise(of: Void.self)
8093
self.channel.close(promise: promise)
8194
return promise.futureResult
8295
}
96+
.map { self.logger.debug("Connection closed.") }
8397
}
8498

8599
/// See `RedisConnection.makePipeline()`.
86100
public func makePipeline() -> RedisPipeline {
87-
return NIORedisPipeline(channel: channel)
101+
var logger = Logger(label: "NIORedis.Pipeline")
102+
logger[metadataKey: loggingKeyID] = self.logger[metadataKey: loggingKeyID]
103+
return NIORedisPipeline(channel: channel, logger: logger)
88104
}
89105

90106
/// See `RedisCommandExecutor.send(command:with:)`.
91107
public func send(command: String, with arguments: [RESPValueConvertible] = []) -> EventLoopFuture<RESPValue> {
92-
guard !_isClosed.load() else { return channel.eventLoop.makeFailedFuture(RedisError.connectionClosed) }
108+
guard !_isClosed.load() else {
109+
logger.error("Received command when connection is closed.")
110+
return channel.eventLoop.makeFailedFuture(RedisError.connectionClosed)
111+
}
93112

94113
let args = arguments.map { $0.convertedToRESPValue() }
95114

@@ -99,6 +118,12 @@ public final class NIORedisConnection: RedisConnection {
99118
promise: promise
100119
)
101120

121+
promise.futureResult.whenComplete { result in
122+
guard case let .failure(error) = result else { return }
123+
self.logger.error("\(error)")
124+
}
125+
logger.debug("Sending command \"\(command)\" with \(arguments) encoded as \(args)")
126+
102127
_ = channel.writeAndFlush(context)
103128

104129
return promise.futureResult

Sources/NIORedis/RedisPipeline.swift

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Foundation
2+
import Logging
23

34
/// An object that provides a mechanism to "pipeline" multiple Redis commands in sequence,
45
/// providing an aggregate response of all the Redis responses for each individual command.
@@ -36,6 +37,7 @@ public protocol RedisPipeline {
3637
}
3738

3839
public final class NIORedisPipeline {
40+
private var logger: Logger
3941
/// The channel being used to send commands with.
4042
private let channel: Channel
4143

@@ -44,9 +46,13 @@ public final class NIORedisPipeline {
4446

4547
/// Creates a new pipeline queue that will write to the channel provided.
4648
/// - Parameter channel: The `Channel` to write to.
47-
public init(channel: Channel) {
49+
public init(channel: Channel, logger: Logger = Logger(label: "NIORedis.Pipeline")) {
4850
self.channel = channel
51+
self.logger = logger
4952
self.queuedCommandResults = []
53+
54+
self.logger[metadataKey: "RedisPipeline"] = "\(UUID())"
55+
self.logger.debug("Pipeline created.")
5056
}
5157
}
5258

@@ -62,22 +68,30 @@ extension NIORedisPipeline: RedisPipeline {
6268
// We are passing ourselves in as the executor instance,
6369
// and our implementation of `RedisCommandExecutor.send(command:with:) handles the actual queueing.
6470
_ = operation(self)
71+
logger.debug("Command queued. Pipeline size: \(count)")
6572
return self
6673
}
6774

6875
/// See `RedisPipeline.execute()`.
6976
/// - Important: If any of the commands fail, the remaining commands will not execute and the `EventLoopFuture` will fail.
7077
public func execute() -> EventLoopFuture<[RESPValue]> {
71-
channel.flush()
72-
7378
let response = EventLoopFuture<[RESPValue]>.reduce(
7479
into: [],
7580
queuedCommandResults,
7681
on: channel.eventLoop,
7782
{ (results, response) in results.append(response) }
7883
)
7984

80-
response.whenComplete { _ in self.queuedCommandResults = [] }
85+
response.whenComplete { result in
86+
self.queuedCommandResults = []
87+
88+
switch result {
89+
case .failure(let error): self.logger.error("\(error)")
90+
case .success: self.logger.debug("Pipeline executed.")
91+
}
92+
}
93+
94+
channel.flush()
8195

8296
return response
8397
}
@@ -101,6 +115,8 @@ extension NIORedisPipeline: RedisCommandExecutor {
101115

102116
queuedCommandResults.append(promise.futureResult)
103117

118+
logger.debug("Enqueuing command \"\(command)\" with \(arguments) encoded as \(args)")
119+
104120
_ = channel.write(context)
105121

106122
return promise.futureResult

0 commit comments

Comments
 (0)