Hi, Racket Discourse.
I have a very simple application, with a small set of log-sources (services
and schedule
), so I have only two loggers provided to the rest of the application, along with the starting-of and shutting-down of the log-receiver threads, which are handled at the entry/exit-point of the application.
At the base, the two loggers are children of a terminal-logger
, which receives all the logs to be displayed to the console as the application is running.
This works fine when the application is properly started, but when I am testing individual parts, I would like to obviously see the results of these logs, and I am not a fan of riddling everything with print expressions.
I thought of just unwrapping the procedure for starting the terminal receiver, but because this produces a thread, and the module is required many times over in my application, I am wondering if this is a silly idea. Will this spawn new threads each time?
The other idea I had, was to wrap log-message
and have that check whether the application is in development
mode, and then dispatch anything to the console when it is, but this also seems yucky, because I have to change all the existing call-sites.
#lang racket
(require "../data.rkt"
"../helpers/database-helper.rkt")
(provide start-loggers
schedule-logger
services-logger)
(define terminal-logger (make-logger 'terminal))
(define schedule-logger (make-logger 'schedule terminal-logger))
(define services-logger (make-logger 'services terminal-logger))
(define logger-custodian (make-custodian))
(define (start-receiver-thread
#:logger logger
#:level level
#:handler handler)
(define receiver (make-log-receiver logger level))
(parameterize ([current-custodian logger-custodian])
(thread
(thunk (let receiver-loop ()
(sync
; end when the thread receives the '(stop) message
(handle-evt (thread-receive-evt) void)
; otherwise, continue
(handle-evt
receiver
(match-lambda
[(vector level message data topic)
(handler level message data topic)
(receiver-loop)]))))))))
(define (start-database-log-receiver logger level)
(start-receiver-thread
#:logger logger
#:level level
#:handler
(lambda (level message data topic)
(define now (current-seconds))
(with-db-connection (db:connect)
; the `conn` is syntax if that seems weird
(db:insert-log conn now (~a level) (~a topic) message)))))
;;
;; This is the culprit
;;
(define terminal-log-receiver
(start-receiver-thread
#:logger terminal-logger
#:level 'debug
#:handler
(lambda (level message data topic)
(displayln (format "[~a]\n~a\n" level message)))))
(define (start-schedule-log-receiver)
(start-database-log-receiver schedule-logger 'debug))
(define (start-services-log-receiver)
(start-database-log-receiver services-logger 'debug))
(define (start-loggers)
(define threads `(,terminal-log-receiver
,(start-schedule-log-receiver)
,(start-services-log-receiver)))
; return the procedure for stopping all the loggers
(thunk
(for ([thd (in-list threads)]) (thread-send thd '(stop)))
(for-each thread-wait threads)
(custodian-shutdown-all logger-custodian)))
Is there a more idiomatic/typical way of approaching this, or could I safely do this without worrying about errant threads all over the place?
Edit:
To elaborate what I mean by "spawn new threads", suppose that I have modules "A.rkt"
and "B.rkt"
, and B
requires some things from A
, causing execution of things in B
to also execute things in A
. Both of these modules require "logging.rkt"
, which has, hypothetically, a thread value defined within it.
My intuition is that there may be separate threads being spawned for A
and B
from their require
, but I can't say for sure.