mirror of https://github.com/nocturn9x/nimd.git
Fixed issues with fcntl and replaced file locking with a duplicate of stderr in O_APPEND mode. Added basic utilities for shutdown/reboot/halt
This commit is contained in:
parent
a93c3c6fd0
commit
d41e67f413
|
@ -23,8 +23,9 @@ import shutdown
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc mainLoop*(logger: Logger, workers: int = 1) =
|
proc mainLoop*(logger: Logger, workers: int = 1, startServices: bool = true) =
|
||||||
## NimD's main execution loop
|
## NimD's main execution loop
|
||||||
|
if startServices:
|
||||||
logger.info("Processing default runlevel")
|
logger.info("Processing default runlevel")
|
||||||
startServices(logger, workers=workers, level=Default)
|
startServices(logger, workers=workers, level=Default)
|
||||||
logger.debug(&"Unblocking signals")
|
logger.debug(&"Unblocking signals")
|
||||||
|
@ -37,10 +38,12 @@ proc mainLoop*(logger: Logger, workers: int = 1) =
|
||||||
serverSocket.listen(5)
|
serverSocket.listen(5)
|
||||||
var clientSocket = newSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
|
var clientSocket = newSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
|
||||||
logger.switchToFile()
|
logger.switchToFile()
|
||||||
|
logger.debug("Entering accept() loop")
|
||||||
while true:
|
while true:
|
||||||
serverSocket.accept(clientSocket)
|
serverSocket.accept(clientSocket)
|
||||||
|
logger.debug(&"Received connection on control socket")
|
||||||
if clientSocket.recv(opType, size=1) == 0:
|
if clientSocket.recv(opType, size=1) == 0:
|
||||||
logger.debug(&"Client has disconnected, waiting for new connection")
|
logger.debug(&"Client has disconnected, waiting for new connections")
|
||||||
continue
|
continue
|
||||||
logger.debug(&"Received operation type '{opType}' via control socket")
|
logger.debug(&"Received operation type '{opType}' via control socket")
|
||||||
# The operation type is a single byte:
|
# The operation type is a single byte:
|
||||||
|
@ -68,4 +71,4 @@ proc mainLoop*(logger: Logger, workers: int = 1) =
|
||||||
logger.critical(&"A critical error has occurred while running, restarting the mainloop in 30 seconds! Error -> {getCurrentExceptionMsg()}")
|
logger.critical(&"A critical error has occurred while running, restarting the mainloop in 30 seconds! Error -> {getCurrentExceptionMsg()}")
|
||||||
sleepSeconds(30)
|
sleepSeconds(30)
|
||||||
# We *absolutely* cannot die
|
# We *absolutely* cannot die
|
||||||
mainLoop(logger)
|
mainLoop(logger, startServices=false)
|
||||||
|
|
|
@ -31,7 +31,7 @@ type ShutdownHandler* = ref object
|
||||||
body*: proc (logger: Logger, code: int)
|
body*: proc (logger: Logger, code: int)
|
||||||
|
|
||||||
|
|
||||||
const reboot_codes = {"poweroff": 0x4321fedc'i64, "restart": 0x01234567'i64, "halt": 0xcdef0123}.toTable()
|
const reboot_codes = {"poweroff": 0x4321fedc'i64, "reboot": 0x01234567'i64, "halt": 0xcdef0123}.toTable()
|
||||||
|
|
||||||
|
|
||||||
proc newShutdownHandler*(body: proc (logger: Logger, code: int)): ShutdownHandler =
|
proc newShutdownHandler*(body: proc (logger: Logger, code: int)): ShutdownHandler =
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Copyright 2021 Mattia Giambirtone & All Contributors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
import net
|
||||||
|
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
var sock = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP)
|
||||||
|
try:
|
||||||
|
sock.connectUnix("/var/run/nimd.sock")
|
||||||
|
except OSError:
|
||||||
|
echo getCurrentExceptionMsg()
|
||||||
|
quit(-1)
|
||||||
|
echo sock.trySend("r")
|
||||||
|
sock.close()
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Copyright 2021 Mattia Giambirtone & All Contributors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
import net
|
||||||
|
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
var sock = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP)
|
||||||
|
try:
|
||||||
|
sock.connectUnix("/var/run/nimd.sock")
|
||||||
|
except OSError:
|
||||||
|
echo getCurrentExceptionMsg()
|
||||||
|
quit(-1)
|
||||||
|
echo sock.trySend("p")
|
||||||
|
sock.close()
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Copyright 2021 Mattia Giambirtone & All Contributors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
import net
|
||||||
|
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
var sock = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP)
|
||||||
|
try:
|
||||||
|
sock.connectUnix("/var/run/nimd.sock")
|
||||||
|
except OSError:
|
||||||
|
echo getCurrentExceptionMsg()
|
||||||
|
quit(-1)
|
||||||
|
echo sock.trySend("r")
|
||||||
|
sock.close()
|
|
@ -38,10 +38,21 @@ type
|
||||||
level*: LogLevel
|
level*: LogLevel
|
||||||
handlers*: seq[LogHandler]
|
handlers*: seq[LogHandler]
|
||||||
|
|
||||||
|
|
||||||
|
proc dup3(a1, a2, a3: cint): cint {.importc.}
|
||||||
|
|
||||||
|
|
||||||
var defaultLevel = LogLevel.Info
|
var defaultLevel = LogLevel.Info
|
||||||
var logFile = "/var/log/nimd"
|
var logFile = "/var/log/nimd"
|
||||||
var logToFileOnly: bool = false
|
var logToFileOnly: bool = false
|
||||||
|
|
||||||
|
## This mess is needed to make sure stderr writes are mostly atomic. Sort of
|
||||||
|
## No error handling yet. Deal with it
|
||||||
|
var customStderrFd = dup(stderr.getFileHandle())
|
||||||
|
discard dup3(stderr.getFileHandle(), customStderrFd, O_APPEND)
|
||||||
|
var customStderr: File
|
||||||
|
discard open(customStderr, customStderrFd, fmAppend)
|
||||||
|
|
||||||
|
|
||||||
proc log(self: Logger, level: LogLevel = defaultLevel, message: string) # Forward declaration
|
proc log(self: Logger, level: LogLevel = defaultLevel, message: string) # Forward declaration
|
||||||
|
|
||||||
|
@ -79,132 +90,91 @@ proc log(self: Logger, level: LogLevel = defaultLevel, message: string) =
|
||||||
# Do NOT touch the alignment offsets or your console output and logs will look like trash
|
# Do NOT touch the alignment offsets or your console output and logs will look like trash
|
||||||
|
|
||||||
|
|
||||||
proc lockFile(logger: Logger, handle: File) =
|
|
||||||
## Locks the given file across the whole system for writing using fcntl()
|
|
||||||
if fcntl(handle.getFileHandle(), F_WRLCK) == -1:
|
|
||||||
setForegroundColor(fgRed)
|
|
||||||
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] Error while locking handle (code {posix.errno}, {posix.strerror(posix.errno)}): output may be mangled""")
|
|
||||||
setForegroundColor(fgDefault)
|
|
||||||
|
|
||||||
|
|
||||||
proc unlockFile(logger: Logger, handle: File) =
|
|
||||||
## Unlocks the given file across the whole system for writing using fcntl()
|
|
||||||
if fcntl(handle.getFileHandle(), F_UNLCK) == -1:
|
|
||||||
setForegroundColor(fgRed)
|
|
||||||
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] Error while unlocking handle (code {posix.errno}, {posix.strerror(posix.errno)}): output may be missing""")
|
|
||||||
setForegroundColor(fgDefault)
|
|
||||||
|
|
||||||
|
|
||||||
proc logTraceStderr(self: LogHandler, logger: Logger, message: string) =
|
proc logTraceStderr(self: LogHandler, logger: Logger, message: string) =
|
||||||
logger.lockFile(stderr)
|
|
||||||
setForegroundColor(fgMagenta)
|
setForegroundColor(fgMagenta)
|
||||||
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} TRACE {"-":>3} ({posix.getpid():03})] {message}""")
|
customStderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} TRACE {"-":>3} ({posix.getpid():03})] {message}""")
|
||||||
setForegroundColor(fgDefault)
|
setForegroundColor(fgDefault)
|
||||||
logger.unlockFile(stderr)
|
|
||||||
|
|
||||||
|
|
||||||
proc logDebugStderr(self: LogHandler, logger: Logger, message: string) =
|
proc logDebugStderr(self: LogHandler, logger: Logger, message: string) =
|
||||||
logger.lockFile(stderr)
|
|
||||||
setForegroundColor(fgCyan)
|
setForegroundColor(fgCyan)
|
||||||
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} DEBUG {"-":>3} ({posix.getpid():03})] {message}""")
|
customStderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} DEBUG {"-":>3} ({posix.getpid():03})] {message}""")
|
||||||
setForegroundColor(fgDefault)
|
setForegroundColor(fgDefault)
|
||||||
logger.unlockFile(stderr)
|
|
||||||
|
|
||||||
|
|
||||||
proc logInfoStderr(self: LogHandler, logger: Logger, message: string) =
|
proc logInfoStderr(self: LogHandler, logger: Logger, message: string) =
|
||||||
logger.lockFile(stderr)
|
|
||||||
setForegroundColor(fgGreen)
|
setForegroundColor(fgGreen)
|
||||||
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} INFO {"-":>4} ({posix.getpid():03})] {message}""")
|
customStderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} INFO {"-":>4} ({posix.getpid():03})] {message}""")
|
||||||
setForegroundColor(fgDefault)
|
setForegroundColor(fgDefault)
|
||||||
logger.unlockFile(stderr)
|
|
||||||
|
|
||||||
|
|
||||||
proc logWarningStderr(self: LogHandler, logger: Logger, message: string) =
|
proc logWarningStderr(self: LogHandler, logger: Logger, message: string) =
|
||||||
logger.lockFile(stderr)
|
|
||||||
setForegroundColor(fgYellow)
|
setForegroundColor(fgYellow)
|
||||||
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} WARNING {"-":>1} ({posix.getpid():03})] {message}""")
|
customStderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} WARNING {"-":>1} ({posix.getpid():03})] {message}""")
|
||||||
setForegroundColor(fgDefault)
|
setForegroundColor(fgDefault)
|
||||||
logger.unlockFile(stderr)
|
|
||||||
|
|
||||||
|
|
||||||
proc logErrorStderr(self: LogHandler, logger: Logger, message: string) =
|
proc logErrorStderr(self: LogHandler, logger: Logger, message: string) =
|
||||||
logger.lockFile(stderr)
|
|
||||||
setForegroundColor(fgRed)
|
setForegroundColor(fgRed)
|
||||||
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] {message}""")
|
customStderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] {message}""")
|
||||||
setForegroundColor(fgDefault)
|
setForegroundColor(fgDefault)
|
||||||
logger.unlockFile(stderr)
|
|
||||||
|
|
||||||
|
|
||||||
proc logCriticalStderr(self: LogHandler, logger: Logger, message: string) =
|
proc logCriticalStderr(self: LogHandler, logger: Logger, message: string) =
|
||||||
logger.lockFile(stderr)
|
|
||||||
setForegroundColor(fgYellow)
|
setForegroundColor(fgYellow)
|
||||||
setBackgroundColor(bgRed)
|
setBackgroundColor(bgRed)
|
||||||
stderr.write(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<4} {"-":>1} CRITICAL {"-":>2} ({posix.getpid():03})]""")
|
customStderr.write(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<4} {"-":>1} CRITICAL {"-":>2} ({posix.getpid():03})]""")
|
||||||
setBackgroundColor(bgDefault)
|
setBackgroundColor(bgDefault)
|
||||||
stderr.writeLine(&""" {message}""")
|
customStderr.writeLine(&""" {message}""")
|
||||||
setForegroundColor(fgDefault)
|
setForegroundColor(fgDefault)
|
||||||
logger.unlockFile(stderr)
|
|
||||||
|
|
||||||
|
|
||||||
proc logFatalStderr(self: LogHandler, logger: Logger, message: string) =
|
proc logFatalStderr(self: LogHandler, logger: Logger, message: string) =
|
||||||
logger.lockFile(stderr)
|
|
||||||
setForegroundColor(fgBlack)
|
setForegroundColor(fgBlack)
|
||||||
setBackgroundColor(bgRed)
|
setBackgroundColor(bgRed)
|
||||||
stderr.write(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<5} {"-":>1} {"":>1} FATAL {"-":>3} ({posix.getpid():03})]""")
|
customStderr.write(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<5} {"-":>1} {"":>1} FATAL {"-":>3} ({posix.getpid():03})]""")
|
||||||
setForegroundColor(fgRed)
|
setForegroundColor(fgRed)
|
||||||
setBackgroundColor(bgDefault)
|
setBackgroundColor(bgDefault)
|
||||||
stderr.writeline(&""" {message}""")
|
customStderr.writeline(&""" {message}""")
|
||||||
setForegroundColor(fgDefault)
|
setForegroundColor(fgDefault)
|
||||||
logger.unlockFile(stderr)
|
|
||||||
|
|
||||||
|
|
||||||
proc logTraceFile(self: LogHandler, logger: Logger, message: string) =
|
proc logTraceFile(self: LogHandler, logger: Logger, message: string) =
|
||||||
var self = StreamHandler(self)
|
var self = StreamHandler(self)
|
||||||
logger.lockFile(self.file)
|
|
||||||
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} TRACE {"-":>3} ({posix.getpid():03})] {message}""")
|
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} TRACE {"-":>3} ({posix.getpid():03})] {message}""")
|
||||||
logger.unlockFile(self.file)
|
|
||||||
|
|
||||||
|
|
||||||
proc logDebugFile(self: LogHandler, logger: Logger, message: string) =
|
proc logDebugFile(self: LogHandler, logger: Logger, message: string) =
|
||||||
var self = StreamHandler(self)
|
var self = StreamHandler(self)
|
||||||
logger.lockFile(self.file)
|
|
||||||
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} DEBUG {"-":>3} ({posix.getpid():03})] {message}""")
|
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} DEBUG {"-":>3} ({posix.getpid():03})] {message}""")
|
||||||
logger.unlockFile(self.file)
|
|
||||||
|
|
||||||
|
|
||||||
proc logInfoFile(self: LogHandler, logger: Logger, message: string) =
|
proc logInfoFile(self: LogHandler, logger: Logger, message: string) =
|
||||||
var self = StreamHandler(self)
|
var self = StreamHandler(self)
|
||||||
logger.lockFile(self.file)
|
|
||||||
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} INFO {"-":>4} ({posix.getpid():03})] {message}""")
|
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} INFO {"-":>4} ({posix.getpid():03})] {message}""")
|
||||||
logger.unlockFile(self.file)
|
|
||||||
|
|
||||||
|
|
||||||
proc logWarningFile(self: LogHandler, logger: Logger, message: string) =
|
proc logWarningFile(self: LogHandler, logger: Logger, message: string) =
|
||||||
var self = StreamHandler(self)
|
var self = StreamHandler(self)
|
||||||
logger.lockFile(self.file)
|
|
||||||
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} WARNING {"-":>1} ({posix.getpid():03})] {message}""")
|
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} WARNING {"-":>1} ({posix.getpid():03})] {message}""")
|
||||||
logger.unlockFile(self.file)
|
|
||||||
|
|
||||||
|
|
||||||
proc logErrorFile(self: LogHandler, logger: Logger, message: string) =
|
proc logErrorFile(self: LogHandler, logger: Logger, message: string) =
|
||||||
var self = StreamHandler(self)
|
var self = StreamHandler(self)
|
||||||
logger.lockFile(self.file)
|
|
||||||
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] {message}""")
|
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] {message}""")
|
||||||
logger.unlockFile(self.file)
|
|
||||||
|
|
||||||
|
|
||||||
proc logCriticalFile(self: LogHandler, logger: Logger, message: string) =
|
proc logCriticalFile(self: LogHandler, logger: Logger, message: string) =
|
||||||
var self = StreamHandler(self)
|
var self = StreamHandler(self)
|
||||||
logger.lockFile(self.file)
|
|
||||||
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<4} {"-":>1} CRITICAL {"-":>2} ({posix.getpid():03})] {message}""")
|
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<4} {"-":>1} CRITICAL {"-":>2} ({posix.getpid():03})] {message}""")
|
||||||
logger.unlockFile(self.file)
|
|
||||||
|
|
||||||
|
|
||||||
proc logFatalFile(self: LogHandler, logger: Logger, message: string) =
|
proc logFatalFile(self: LogHandler, logger: Logger, message: string) =
|
||||||
var self = StreamHandler(self)
|
var self = StreamHandler(self)
|
||||||
logger.lockFile(self.file)
|
|
||||||
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<5} {"-":>1} {"":>1} FATAL {"-":>3} ({posix.getpid():03})] {message}""")
|
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<5} {"-":>1} {"":>1} FATAL {"-":>3} ({posix.getpid():03})] {message}""")
|
||||||
logger.unlockFile(self.file)
|
|
||||||
|
|
||||||
|
|
||||||
proc switchToFile*(self: Logger) =
|
proc switchToFile*(self: Logger) =
|
||||||
|
|
Loading…
Reference in New Issue