Skip to content

Commit a5111ed

Browse files
committed
A new version of slog
This does a lot less pathname handling than the previous one. It may not be completely compatible, especially around when log files are created and so on. But I think it is better. This has not yet had any real testing (it does pass its tests).
1 parent 3e22f2d commit a5111ed

8 files changed

+349
-179
lines changed

README.md

+39-16
Original file line numberDiff line numberDiff line change
@@ -1666,6 +1666,8 @@ It originated as part of a lambda list parser, and betrays that heritage to some
16661666

16671667
*Any* predicate works: the predicates and predicate combinators it comes with are just the ones I wrote. Many of the ones that don't exist don't exist because there already *are* predicates which match, for instance, keywords, or the empty list and so on.
16681668

1669+
There arguably should be spread versions of many of these combinators, so if you have a list of predicates you could say, for instance `(all-of* preds)` rather than `(apply #'all-of preds)`. There might be in future.
1670+
16691671
### Package, module, dependencies
16701672
`spam` lives in `org.tfeb.hax.spam` and provides `:org.tfeb.hax.spam`. It requires `simple-loops` and will attempt to load it if `require-module` is present.
16711673

@@ -1874,7 +1876,7 @@ The `logging` macro and the `slog-to` generic function know about *log destinati
18741876
The destinations that `slog` knows about already are:
18751877

18761878
- streams -- the report is written to the stream;
1877-
- strings or pathnames designate filenames which are opened if needed and then the report written to the resulting stream (see below on file handling);
1879+
- strings or pathnames designate filenames which are opened if needed, with any needed directories being created, and then the report written to the resulting stream (see below on file handling);
18781880
- function designators -- the function is called to handle the entry;
18791881
- the symbol `nil` causes the entry to be discarded;
18801882
- any other destination type invokes the fallback handler (see below).
@@ -1884,7 +1886,7 @@ Generally `slog-to` tries to return the condition object, however in the case wh
18841886
You can define methods on `slog-to` to handle other destination classes, or indeed log entry classes. Caveats:
18851887

18861888
- `slog-to` has an argument precedence order of `(datum destination)`, which is the inverse of the default;
1887-
- methods on `slog-to` should not be defined only for classes you define, and not for any standard CL classes or any classes defined by `slog` itself.
1889+
- methods on `slog-to` should be defined only for classes you define, and not for any standard CL classes or any classes defined by `slog` itself.
18881890

18891891
**`*fallback-log-destination-handler*`** is the fallback log destination handler. If bound to a function, then `slog-to` will, by default, call this function with three arguments: the destination, the log entry, and a list of other arguments passed to `slog-to`. The function is assumed to handle the entry and its return value or values are returned by `slog-to`. The default value of this variable is `nil` which will cause `slog-to`to signal an error.
18901892

@@ -1903,7 +1905,7 @@ If you want to have fine-grained control over log entry formats then two possibl
19031905
The second approach allows you, for instance, to select locale-specific formats by passing keyword arguments to specify non-default locales to `slog-to`, rather than just relying on its class alone[^19].
19041906

19051907
### The `logging` macro
1906-
**`(logging ([(typespec destination ...) ...]) form ...)`** establishes dynamic handlers for log entries which will log to the values of the specified destinations. Each `typespec` is as for `handler-bind`, except that the type `t` is rewritten as `log-entry`, which makes things easier to write. Any type mentioned in `typespec` must be a subtype of `log-entry`. The value of each destination is then found, pathnames being canonicalized (see below for pathname handling) and these values are used as the destinations for calls to `slog-to`. As an example the expansion of the following form:
1908+
**`(logging ([(typespec destination ...) ...]) form ...)`** establishes dynamic handlers for log entries which will log to the values of the specified destinations. Each `typespec` is as for `handler-bind`, except that the type `t` is rewritten as `log-entry`, which makes things easier to write. Any type mentioned in `typespec` must be a subtype of `log-entry`. The value of each destination is then found, with special handling for pathnames (see below) and these values are used as the destinations for calls to `slog-to`. As an example the expansion of the following form:
19071909

19081910
```lisp
19091911
(logging ((t "foo"
@@ -1924,7 +1926,7 @@ is similar to this:
19241926
...))
19251927
```
19261928

1927-
where `d1`, `d2` and `e` are gensyms, and the (internal) `canonicalize-destination` function will return an absolute pathname for arguments which are strings or pathnames and leave others untouched. See below for `closing-opened-log-files`.
1929+
where `d1`, `d2` and `e` are gensyms, and the (internal) `canonicalize-destination` deals with destinations which represent files. See below for `closing-opened-log-files`.
19281930

19291931
The result of this is that `logging` behaves as if the destinations were lexically scoped. So for instance this will not 'work':
19301932

@@ -1959,20 +1961,22 @@ One important reason for this behaviour of `logging` is to deal with this proble
19591961
19601962
(defun foo ()
19611963
(slog "foo")
1962-
... change working directory ...
1964+
... change what file "foo.log" would refer to ...
19631965
(slog "bar"))
19641966
```
19651967

19661968
Because the absolute pathname of `foo.log`is computed and stored at the point of the logging macro you won't end up logging to multiple files: all the log entries will go to whatever the canonical version of `foo.log` was at the point that `logging` appeared.
19671969

19681970
Finally note that calls to `slog` are completely legal but will do nothing outside the dynamic extent of a `logging` macro[^20], but `slog-to` will work quite happily and will write log entries. This includes when given pathname arguments: it is perfectly legal to write code which just calls `(slog-to "/my/file.log" "a message")`. See below on file handling.
19691971

1972+
Note that destinations which correspond to files (pathnames and strings) are opened by `logging`, with any needed directories being created and so on. This means that if those files *can't* be opened then you'll get an error immediately, rather than on the first call to `slog`.
1973+
19701974
### File handling
19711975
For log destinations which correspond to files, `slog` goes to some lengths to try and avoid open streams leaking away and to make sure there is a single stream open to each log file. This is not as simple as it looks as `slog-to` can take a destination argument which is a filename, so that users don't have to write a mass of code which handles streams, and there's no constraint that `slog-to` must be called within the dynamic extent of a `logging` form.
19721976

1973-
Behind the scenes there is a map between absolute pathnames and the corresponding streams. Both `logging` and `slog-to` convert any destinations which look like pathnames to absolute pathnames if they are not already and store them in this map. There are then functions and a macro which handle this map for you.
1977+
Behind the scenes there is a stack of log file objects which know both their true pathnames (from `truename`), as well as the corresponding stream if they're open, thus providing a map between truenames and streams. When given destinations which are file designators both `logging` and `slog-to` will first probe files to see if they exist, and use the corresponding true name (or the pathname if the file is not found) to find or create suitable entries in this map. There are then a number of utilities to deal with this map.
19741978

1975-
**`closing-opened-log-files`** is a macro which establishes a saved state of the map of files to streams. On exit it will close any log file streams added to the map within its dynamic extent (using `unwind-protect` in the obvious way) and restore the saved state (this is all done using special variables in the obvious way so is thread-safe). So for instance
1979+
**`closing-opened-log-files`** is a macro which establishes a saved state of the map between true names and streams. On exit it will close any log file streams added to the map within its dynamic extent (using `unwind-protect` in the obvious way) and restore the saved state (this is all done using special variables in the obvious way so is thread-safe). So for instance
19761980

19771981
```lisp
19781982
(closing-opened-log-files ()
@@ -1986,24 +1990,28 @@ will close the open stream to `foo.log` if `slog-to` opened it. The full syntax
19861990
...)
19871991
```
19881992

1989-
Where `abort`is simply passed to the `close` calls. `reporter`, if given, should be a function which will be called with the name of each file close.
1993+
Where `abort`is simply passed to the `close` calls. `reporter`, if given, should be a function which will be called with the name of each file closed.
19901994

19911995
`logging` expands into something which uses `closing-opened-log-files`.
19921996

1993-
**`current-log-files`** is a function which will return two values: a list of the current open log files and a list of the current log files which have been closed. It has a single keyword argument:
1997+
**`current-log-files`** is a function which will return two values: lists of the true names of currently open log files, and of currently closed log files. It has a single keyword argument:
19941998

19951999
-`all`, if given as true will cause it to return the elements of the whole list, while otherwise it will return lists just back to the closest surrounding `closing-opened-log-files`.
19962000

1997-
**`close-open-log-files`** will close currently open log files, returning two lists: the list of files that were open and the list of files which were already closed. It takes three keyword arguments:
2001+
**`close-open-log-files`** will close currently open log files, returning two lists: the list of files that were open and the list of files which were already closed. It takes four keyword arguments:
19982002

19992003
- `abort`is passed as the `abort` keyword to `close`;
20002004
- `all` is as for `current-log-files`;
2001-
- `reset`, if true, will reset the map to the save point of the nearest `closing-open-log-files` (or completely if there is no nearest).
2005+
- `reset`, if given, will reset the map to the save point of the nearest `closing-open-log-files` (or completely if there is no nearest, or if `all` is given);
2006+
- `test` should be a function of one argument, a pathname, which will cause only those log files for which the function succeeds to be closed.
2007+
2008+
You can't use both `test` and `reset` because it would leak streams.
20022009

2003-
**`flush-open-log-file-streams`** will flush open log file streams to their files. It returns a list of the filenames streams were flushed to. It has two keyword arguments:
2010+
**`flush-open-log-file-streams`** will flush open log file streams to their files. It returns a list of the filenames streams were flushed to. It has three keyword arguments:
20042011

20052012
- `all` is as for `current-log-files`;
2006-
- `wait`, if true, will cause it to call `finish-output` rather than `force-output`.
2013+
- `wait`, if true, will cause it to call `finish-output` rather than `force-output`;
2014+
- `test` is as for `close-open-log-files`, and only the files which pass the test will be flushed.
20072015

20082016
Note that it is perfectly OK to close a log file stream whenever you like: the system will simply reopen the file if it needs to. This is fine for instance:
20092017

@@ -2014,7 +2022,7 @@ Note that it is perfectly OK to close a log file stream whenever you like: the s
20142022
(slog "bar"))
20152023
```
20162024

2017-
What will happen is that the handler will open the file when handling the first condition raised by `slog`, the file will be close, and then the handler will reopen it. You can see this at work: given
2025+
What will happen is that the handler will open the file when handling the first condition raised by `slog`, the file will be closes, and then the handler will reopen it. You can see this at work: given
20182026

20192027
```lisp
20202028
(defun print-log-files (prefix)
@@ -2047,7 +2055,22 @@ nil
20472055

20482056
If you call `slog-to` with a filename destination*outside* the dynamic extent of `logging` or `closing-opened-log-files` then you perhaps want to call `(close-open-log-files :reset t)` every once in a while as well (the `reset` argument doesn't really matter, but it cleans up the map).
20492057

2050-
Finally note that this mechanism is only about filename destinations: if you log to stream destinations you need to manage the streams as you normally would. The purpose of it is so you can simply not have to worry about the streams but just specify what log file(s) you want written.
2058+
Finally note that all this mechanism is only about filename destinations: if you log to stream destinations you need to manage the streams as you normally would. The purpose of it is so you can simply not have to worry about the streams but just specify what log file(s) you want written.
2059+
2060+
### Checking the log file map is sane
2061+
**`log-files-sane-p`** is a function which will check lists of files for sanity. It has two arguments: a list of true names of open log files, and a list for closed log files. These are just the two lists that `current-log-files` returns. It will return `nil` if there is some problem such as duplicate entries, or files appearing on both lists. This should not be able to happen, but it's better to be able to know if it does. In addition it has two keyword arguments:
2062+
2063+
- `warn` will signal a warning for each wrong thing it finds;
2064+
- `error` will cause it to signal an error rather than returning `nil`.
2065+
2066+
So, for instance
2067+
2068+
```lisp
2069+
(multiple-value-bind (opened closed) (current-log-files :all t)
2070+
(log-files-sane-p opened closed :warn t :error t))
2071+
```
2072+
2073+
would cause a suitable error (preceded by warnings) if anything was wrong.
20512074

20522075
### Precision time
20532076
`slog` makes an attempt to write log entries with a 'precision' version of CL's universal time. It does this by calibrating internal real time against universal time when loaded (this means that loading `slog` takes at least a second and can take up to two seconds or more) and then using this calibration to record times with the precision of internal time. There is one function which is exposed for this.
@@ -2100,7 +2123,7 @@ There are some sanity tests for this code which are run on loading `slog`, becau
21002123
You can use `get-precision-universal-time` to write your own formatters, using `log-entry-internal-time` to get the time the entry was created.
21012124

21022125
### Notes
2103-
`slog` needs to know the current working directory in order to make pathnames absolute. By default it uses ASDF's function for this, but if you don't use ASDF it has its own which will work for a small number of implementations and has a terrible (and wrong) fallback. It will warn at compile/load time if it needs to use the terrible fallback: if it does this tell me how to know this in your implementation and I'll add a case for it.
2126+
A previous version `slog` handled files rather differently: it tried to delay creating or opening them as long as possible, and thus needed to be able to find an absolute pathname for a file which it had not opened or created, requiring it to have a notion of the current directory which was better than `*default-pathname-defaults*`. This is now all gone: the current version simply treats pathnames as they are.
21042127

21052128
Condition objects are meant to be immutable (from the [CLHS](http://www.lispworks.com/documentation/HyperSpec/Body/m_defi_5.htm "define-condition"):
21062129

VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
6.0.0
1+
7.0.0

org.tfeb.hax.asd

+1-3
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
:author "Tim Bradshaw"
1010
:license "MIT"
1111
:homepage "https://github.com/tfeb/tfeb-lisp-hax"
12-
:depends-on (#-LispWorks "closer-mop"
13-
"org.tfeb.tools.feature-expressions")
12+
:depends-on (#-LispWorks "closer-mop")
1413
:in-order-to ((test-op (load-op "org.tfeb.hax/test")))
1514
:components
1615
((:file "collecting")
@@ -39,7 +38,6 @@
3938
(:file "metatronic"
4039
:depends-on ("utilities"))
4140
(:file "slog"
42-
;; this is where the dependency on feature-expressions is
4341
:depends-on ("simple-loops" "collecting" "spam"
4442
"metatronic"))
4543
(:file "hax-cometh"

org.tfeb.hax.slog.asd

+1-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
("org.tfeb.hax.simple-loops"
1919
"org.tfeb.hax.collecting"
2020
"org.tfeb.hax.spam"
21-
"org.tfeb.hax.metatronic"
22-
"org.tfeb.tools.feature-expressions")
21+
"org.tfeb.hax.metatronic")
2322
:components
2423
((:file "slog")))

0 commit comments

Comments
 (0)