@larablox/monolog 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/README.md +23 -11
  2. package/out/Monolog/Handler/AbstractProcessingHandler.d.ts +3 -1
  3. package/out/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.d.ts +19 -0
  4. package/out/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.luau +30 -0
  5. package/out/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.d.ts +29 -0
  6. package/out/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.luau +61 -0
  7. package/out/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.d.ts +14 -0
  8. package/out/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.luau +32 -0
  9. package/out/Monolog/Handler/FingersCrossedHandler.d.ts +107 -0
  10. package/out/Monolog/Handler/FingersCrossedHandler.luau +208 -0
  11. package/out/Monolog/Handler/FormattableHandlerInterface.d.ts +23 -0
  12. package/out/Monolog/Handler/FormattableHandlerInterface.luau +36 -0
  13. package/out/Monolog/Handler/GroupHandler.d.ts +49 -0
  14. package/out/Monolog/Handler/GroupHandler.luau +139 -0
  15. package/out/Monolog/Handler/HandlerInterface.d.ts +10 -0
  16. package/out/Monolog/Handler/HandlerInterface.luau +36 -1
  17. package/out/Monolog/Handler/ProcessableHandlerInterface.d.ts +26 -0
  18. package/out/Monolog/Handler/ProcessableHandlerInterface.luau +15 -0
  19. package/out/Monolog/Handler/RobloxConsoleHandler.d.ts +19 -46
  20. package/out/Monolog/Handler/RobloxConsoleHandler.luau +28 -99
  21. package/out/Monolog/Handler/WhatFailureGroupHandler.d.ts +26 -0
  22. package/out/Monolog/Handler/WhatFailureGroupHandler.luau +85 -0
  23. package/package.json +11 -3
package/README.md CHANGED
@@ -27,7 +27,7 @@ import { Level } from "@larablox/monolog/out/Monolog/Level";
27
27
 
28
28
  // create a log channel
29
29
  const log = new Logger("name");
30
- log.pushHandler(new RobloxConsoleHandler({}, Level.Warning));
30
+ log.pushHandler(new RobloxConsoleHandler(Level.Warning));
31
31
 
32
32
  // add records to the log
33
33
  log.warning("Foo");
@@ -39,36 +39,48 @@ so consumers deep-import each class the way the example above does.
39
39
 
40
40
  ## What's included
41
41
 
42
- - `Logger`, `Level`, `LogRecord`, `Registry`, `Utils`, `ResettableInterface`
43
- - Handlers: `NullHandler`, `RobloxConsoleHandler` (the output-console adaptation
44
- of `PHPConsoleHandler`), and `TestHandler` for your own tests
42
+ - Core: `Logger`, `LoggerInterface` (the PSR-3 shape), `Level`, `LogRecord`,
43
+ `Registry`, `Utils`, `ResettableInterface`
44
+ - Handlers: `NullHandler`, `RobloxConsoleHandler` (writes formatted records to
45
+ the Roblox output console), and `TestHandler` for your own tests
46
+ - Wrapping handlers: `GroupHandler`, `WhatFailureGroupHandler`,
47
+ `FingersCrossedHandler` (with `ErrorLevelActivationStrategy` and
48
+ `ChannelLevelActivationStrategy`)
49
+ - Handler base classes and interfaces, for writing your own: `Handler`,
50
+ `AbstractHandler`, `AbstractProcessingHandler`, `HandlerInterface`,
51
+ `FormattableHandlerInterface`, `ProcessableHandlerInterface`
45
52
  - Formatters: `NormalizerFormatter`, `LineFormatter`, `JsonFormatter`,
46
- `ScalarFormatter`
53
+ `ScalarFormatter`, `HtmlFormatter`, and `FormatterInterface`
47
54
  - Processors: `PsrLogMessageProcessor`, `ClosureContextProcessor`,
48
55
  `IntrospectionProcessor`, `MemoryUsageProcessor`, `MemoryPeakUsageProcessor`,
49
- `ProcessIdProcessor`, `TagProcessor`, `UidProcessor`
56
+ `ProcessIdProcessor`, `TagProcessor`, `UidProcessor`, and
57
+ `ProcessorInterface`
50
58
  - Attributes: `WithMonologChannel`, `AsMonologProcessor`
59
+ - Test helpers: `Test/MonologTestCase` (plus `Test/TestCase`, upstream's
60
+ deprecated alias for it), shipped in the published package the same way
61
+ upstream ships them, because consumers use them in their own suites
51
62
 
52
63
  ## Submitting bugs and feature requests
53
64
 
54
65
  Bugs and feature requests are tracked on
55
66
  [GitHub](https://github.com/larablox/monolog/issues).
56
67
 
57
- ### Requirements
68
+ ## Requirements
58
69
 
59
70
  - TypeScript 5.x compiled with [roblox-ts](https://roblox-ts.com/) `^3.0`
60
71
  - A Rojo-synced Roblox place to run the compiled output in
61
72
 
62
- ### Framework Integration
73
+ ## Framework Integration
63
74
 
64
75
  [`larablox/framework`](https://github.com/larablox/framework)'s
65
76
  `Illuminate\Log` is built on this package.
66
77
 
67
- ### License
78
+ ## License
68
79
 
69
80
  MIT, matching upstream Monolog.
70
81
 
71
- ### Acknowledgements
82
+ ## Acknowledgements
72
83
 
73
84
  This is a TypeScript/roblox-ts port of [Monolog](https://github.com/Seldaek/monolog)
74
- by Jordi Boggiano, adapted to run on the Roblox platform as faithfully as it allows.
85
+ by Jordi Boggiano, adapted to run on the Roblox platform as faithfully as it
86
+ allows.
@@ -1,12 +1,14 @@
1
1
  import { AbstractHandler } from "./AbstractHandler";
2
+ import type { FormattableHandlerInterface } from "./FormattableHandlerInterface";
2
3
  import type { FormatterInterface } from "../Formatter/FormatterInterface";
3
4
  import type { LogRecord } from "../LogRecord";
5
+ import type { ProcessableHandlerInterface } from "./ProcessableHandlerInterface";
4
6
  import type { Processor } from "../Processor/ProcessorInterface";
5
7
  /**
6
8
  * PHP: `Monolog\Handler\AbstractProcessingHandler`, which folds in
7
9
  * `ProcessableHandlerTrait` and `FormattableHandlerTrait`.
8
10
  */
9
- export declare abstract class AbstractProcessingHandler extends AbstractHandler {
11
+ export declare abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface {
10
12
  protected processors: Processor[];
11
13
  protected formatter?: FormatterInterface;
12
14
  /** Handles a record. */
@@ -0,0 +1,19 @@
1
+ import type { LogRecord } from "../../LogRecord";
2
+ /**
3
+ * PHP: `Monolog\Handler\FingersCrossed\ActivationStrategyInterface`.
4
+ *
5
+ * Interface for activation strategies for the `FingersCrossedHandler`.
6
+ */
7
+ export interface ActivationStrategyInterface {
8
+ /** Returns whether the given record activates the handler. */
9
+ isHandlerActivated(record: LogRecord): boolean;
10
+ }
11
+ /**
12
+ * True when the given value implements `ActivationStrategyInterface`.
13
+ *
14
+ * `FingersCrossedHandler`'s constructor takes either a strategy or a bare
15
+ * level and branches on `instanceof`; there is no `instanceof` for an
16
+ * interface here, so this checks structurally for the one callable member, as
17
+ * `isResettable()` (`ResettableInterface.ts`) already does for its own.
18
+ */
19
+ export declare function isActivationStrategy(value: unknown): value is ActivationStrategyInterface;
@@ -0,0 +1,30 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ --[[
3
+ *
4
+ * PHP: `Monolog\Handler\FingersCrossed\ActivationStrategyInterface`.
5
+ *
6
+ * Interface for activation strategies for the `FingersCrossedHandler`.
7
+
8
+ ]]
9
+ --[[
10
+ *
11
+ * True when the given value implements `ActivationStrategyInterface`.
12
+ *
13
+ * `FingersCrossedHandler`'s constructor takes either a strategy or a bare
14
+ * level and branches on `instanceof`; there is no `instanceof` for an
15
+ * interface here, so this checks structurally for the one callable member, as
16
+ * `isResettable()` (`ResettableInterface.ts`) already does for its own.
17
+
18
+ ]]
19
+ local function isActivationStrategy(value)
20
+ local _value = value
21
+ if not (type(_value) == "table") then
22
+ return false
23
+ end
24
+ local candidate = value
25
+ local _isHandlerActivated = candidate.isHandlerActivated
26
+ return type(_isHandlerActivated) == "function"
27
+ end
28
+ return {
29
+ isActivationStrategy = isActivationStrategy,
30
+ }
@@ -0,0 +1,29 @@
1
+ import type { ActivationStrategyInterface } from "./ActivationStrategyInterface";
2
+ import type { Level } from "../../Level";
3
+ import type { LogLevel } from "../../LoggerInterface";
4
+ import type { LogRecord } from "../../LogRecord";
5
+ /**
6
+ * PHP: `Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy`.
7
+ *
8
+ * Channel and error level based activation strategy. Allows triggering
9
+ * activation based on level per channel: e.g. trigger activation on level
10
+ * `Error` by default, except for records of the `sql` channel, which should
11
+ * trigger activation on level `Warning`.
12
+ *
13
+ * ```ts
14
+ * const activationStrategy = new ChannelLevelActivationStrategy(
15
+ * Level.Critical,
16
+ * { request: Level.Alert, sensitive: Level.Error },
17
+ * );
18
+ * const handler = new FingersCrossedHandler(
19
+ * new RobloxConsoleHandler(),
20
+ * activationStrategy,
21
+ * );
22
+ * ```
23
+ */
24
+ export declare class ChannelLevelActivationStrategy implements ActivationStrategyInterface {
25
+ private readonly defaultActionLevel;
26
+ private readonly channelToActionLevel;
27
+ constructor(defaultActionLevel: Level | LogLevel, channelToActionLevel?: Record<string, Level | LogLevel>);
28
+ isHandlerActivated(record: LogRecord): boolean;
29
+ }
@@ -0,0 +1,61 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local Logger = TS.import(script, script.Parent.Parent.Parent, "Logger").Logger
4
+ --[[
5
+ *
6
+ * PHP: `Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy`.
7
+ *
8
+ * Channel and error level based activation strategy. Allows triggering
9
+ * activation based on level per channel: e.g. trigger activation on level
10
+ * `Error` by default, except for records of the `sql` channel, which should
11
+ * trigger activation on level `Warning`.
12
+ *
13
+ * ```ts
14
+ * const activationStrategy = new ChannelLevelActivationStrategy(
15
+ * Level.Critical,
16
+ * { request: Level.Alert, sensitive: Level.Error },
17
+ * );
18
+ * const handler = new FingersCrossedHandler(
19
+ * new RobloxConsoleHandler(),
20
+ * activationStrategy,
21
+ * );
22
+ * ```
23
+
24
+ ]]
25
+ local ChannelLevelActivationStrategy
26
+ do
27
+ ChannelLevelActivationStrategy = setmetatable({}, {
28
+ __tostring = function()
29
+ return "ChannelLevelActivationStrategy"
30
+ end,
31
+ })
32
+ ChannelLevelActivationStrategy.__index = ChannelLevelActivationStrategy
33
+ function ChannelLevelActivationStrategy.new(...)
34
+ local self = setmetatable({}, ChannelLevelActivationStrategy)
35
+ return self:constructor(...) or self
36
+ end
37
+ function ChannelLevelActivationStrategy:constructor(defaultActionLevel, channelToActionLevel)
38
+ if channelToActionLevel == nil then
39
+ channelToActionLevel = {}
40
+ end
41
+ self.channelToActionLevel = {}
42
+ self.defaultActionLevel = Logger:toMonologLevel(defaultActionLevel)
43
+ for channel, level in pairs(channelToActionLevel) do
44
+ local _channelToActionLevel = self.channelToActionLevel
45
+ local _arg1 = Logger:toMonologLevel(level)
46
+ _channelToActionLevel[channel] = _arg1
47
+ end
48
+ end
49
+ function ChannelLevelActivationStrategy:isHandlerActivated(record)
50
+ local _channelToActionLevel = self.channelToActionLevel
51
+ local _channel = record.channel
52
+ local channelLevel = _channelToActionLevel[_channel]
53
+ if channelLevel ~= nil then
54
+ return record.level >= channelLevel
55
+ end
56
+ return record.level >= self.defaultActionLevel
57
+ end
58
+ end
59
+ return {
60
+ ChannelLevelActivationStrategy = ChannelLevelActivationStrategy,
61
+ }
@@ -0,0 +1,14 @@
1
+ import type { ActivationStrategyInterface } from "./ActivationStrategyInterface";
2
+ import type { Level } from "../../Level";
3
+ import type { LogLevel } from "../../LoggerInterface";
4
+ import type { LogRecord } from "../../LogRecord";
5
+ /**
6
+ * PHP: `Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy`.
7
+ *
8
+ * Error level based activation strategy.
9
+ */
10
+ export declare class ErrorLevelActivationStrategy implements ActivationStrategyInterface {
11
+ private readonly actionLevel;
12
+ constructor(actionLevel: Level | LogLevel);
13
+ isHandlerActivated(record: LogRecord): boolean;
14
+ }
@@ -0,0 +1,32 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local Logger = TS.import(script, script.Parent.Parent.Parent, "Logger").Logger
4
+ --[[
5
+ *
6
+ * PHP: `Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy`.
7
+ *
8
+ * Error level based activation strategy.
9
+
10
+ ]]
11
+ local ErrorLevelActivationStrategy
12
+ do
13
+ ErrorLevelActivationStrategy = setmetatable({}, {
14
+ __tostring = function()
15
+ return "ErrorLevelActivationStrategy"
16
+ end,
17
+ })
18
+ ErrorLevelActivationStrategy.__index = ErrorLevelActivationStrategy
19
+ function ErrorLevelActivationStrategy.new(...)
20
+ local self = setmetatable({}, ErrorLevelActivationStrategy)
21
+ return self:constructor(...) or self
22
+ end
23
+ function ErrorLevelActivationStrategy:constructor(actionLevel)
24
+ self.actionLevel = Logger:toMonologLevel(actionLevel)
25
+ end
26
+ function ErrorLevelActivationStrategy:isHandlerActivated(record)
27
+ return record.level >= self.actionLevel
28
+ end
29
+ end
30
+ return {
31
+ ErrorLevelActivationStrategy = ErrorLevelActivationStrategy,
32
+ }
@@ -0,0 +1,107 @@
1
+ import { Handler } from "./Handler";
2
+ import { Level } from "../Level";
3
+ import type { ActivationStrategyInterface } from "./FingersCrossed/ActivationStrategyInterface";
4
+ import type { FormattableHandlerInterface } from "./FormattableHandlerInterface";
5
+ import type { FormatterInterface } from "../Formatter/FormatterInterface";
6
+ import type { HandlerInterface } from "./HandlerInterface";
7
+ import type { LogLevel } from "../LoggerInterface";
8
+ import type { LogRecord } from "../LogRecord";
9
+ import type { Processor } from "../Processor/ProcessorInterface";
10
+ import type { ProcessableHandlerInterface } from "./ProcessableHandlerInterface";
11
+ import type { ResettableInterface } from "../ResettableInterface";
12
+ /**
13
+ * PHP: the `Closure(LogRecord|null, HandlerInterface): HandlerInterface`
14
+ * factory `FingersCrossedHandler` accepts in place of a handler.
15
+ */
16
+ export type HandlerFactory = (record: LogRecord | undefined, handler: FingersCrossedHandler) => HandlerInterface;
17
+ /**
18
+ * PHP: `Monolog\Handler\FingersCrossedHandler`.
19
+ *
20
+ * Buffers all records until a certain level is reached.
21
+ *
22
+ * The advantage of this approach is that you get no clutter in your log. Only
23
+ * sessions which actually trigger an error (or whatever the
24
+ * `activationStrategy` is) end up in the log, but they contain all records,
25
+ * not only those above the level threshold.
26
+ *
27
+ * There is a `passthruLevel` as well, which means that at the end, even if the
28
+ * handler never got activated, it will still send through log records of at
29
+ * least that level.
30
+ *
31
+ * Upstream gets its processor stack from `ProcessableHandlerTrait`; Luau has
32
+ * no traits, so the trait's members are spelled out here -- see the same note
33
+ * on `GroupHandler` and `AbstractProcessingHandler`.
34
+ */
35
+ export declare class FingersCrossedHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface {
36
+ protected processors: Processor[];
37
+ /** The nested handler, or the factory that will produce it on first use. */
38
+ protected handler: HandlerInterface | HandlerFactory;
39
+ protected activationStrategy: ActivationStrategyInterface;
40
+ protected buffering: boolean;
41
+ protected bufferSize: number;
42
+ protected buffer: LogRecord[];
43
+ protected stopBuffering: boolean;
44
+ protected passthruLevel?: Level;
45
+ protected bubble: boolean;
46
+ /**
47
+ * @param handler Handler or factory `(record | undefined, fingersCrossedHandler)`.
48
+ * @param activationStrategy Strategy determining when this handler takes
49
+ * action, or a level (name or `Level`) at which it is activated.
50
+ * @param bufferSize How many entries to buffer at most; beyond that the
51
+ * oldest items are removed from the buffer. `0` means unbounded.
52
+ * @param bubble Whether the handled messages can bubble up the stack.
53
+ * @param stopBuffering Whether to stop buffering after being triggered.
54
+ * @param passthruLevel Minimum level to always flush to the nested handler
55
+ * on close, even if the strategy never triggered.
56
+ */
57
+ constructor(handler: HandlerInterface | HandlerFactory, activationStrategy?: Level | LogLevel | ActivationStrategyInterface, bufferSize?: number, bubble?: boolean, stopBuffering?: boolean, passthruLevel?: Level | LogLevel);
58
+ /** Always true -- this handler decides what to do with a record later. */
59
+ isHandling(_record: LogRecord): boolean;
60
+ /** Manually activates this handler regardless of the activation strategy. */
61
+ activate(): void;
62
+ /** Handles a record, buffering it until the strategy activates. */
63
+ handle(record: LogRecord): boolean;
64
+ /** Flushes anything left at the passthru level, then closes the nested handler. */
65
+ close(): void;
66
+ /** Resets this handler, its processors, and the nested handler. */
67
+ reset(): void;
68
+ /**
69
+ * Clears the buffer without flushing any messages down to the wrapped
70
+ * handler. Also resets the handler to its initial buffering state.
71
+ */
72
+ clear(): void;
73
+ /**
74
+ * Resets the state of the handler. Stops forwarding records to the wrapped
75
+ * handler.
76
+ */
77
+ private flushBuffer;
78
+ /**
79
+ * Returns the nested handler.
80
+ *
81
+ * If the handler was provided as a factory, this triggers the handler's
82
+ * instantiation.
83
+ *
84
+ * Upstream's `\RuntimeException` for a factory that returns a non-handler
85
+ * is kept -- there is no exception class hierarchy on this platform, so it
86
+ * is a plain `error()` carrying upstream's message.
87
+ */
88
+ getHandler(record?: LogRecord): HandlerInterface;
89
+ /** Adds a processor in the stack. */
90
+ pushProcessor(processor: Processor): this;
91
+ /** Removes the processor on top of the stack and returns it. */
92
+ popProcessor(): Processor | undefined;
93
+ /** Processes a record. */
94
+ protected processRecord(record: LogRecord): LogRecord;
95
+ /** Resets any processors on this handler that support resetting. */
96
+ protected resetProcessors(): void;
97
+ /**
98
+ * Sets the formatter on the nested handler.
99
+ *
100
+ * Upstream's `\UnexpectedValueException` names the offending class with
101
+ * `get_class()`; `Utils.getClass()` is not ported (see `Utils.ts`), so the
102
+ * name comes from the roblox-ts class metatable's `__tostring` instead.
103
+ */
104
+ setFormatter(formatter: FormatterInterface): this;
105
+ /** Gets the formatter of the nested handler. */
106
+ getFormatter(): FormatterInterface;
107
+ }
@@ -0,0 +1,208 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local ErrorLevelActivationStrategy = TS.import(script, script.Parent, "FingersCrossed", "ErrorLevelActivationStrategy").ErrorLevelActivationStrategy
4
+ local Handler = TS.import(script, script.Parent, "Handler").Handler
5
+ local isActivationStrategy = TS.import(script, script.Parent, "FingersCrossed", "ActivationStrategyInterface").isActivationStrategy
6
+ local isFormattable = TS.import(script, script.Parent, "FormattableHandlerInterface").isFormattable
7
+ local isHandler = TS.import(script, script.Parent, "HandlerInterface").isHandler
8
+ local isResettable = TS.import(script, script.Parent.Parent, "ResettableInterface").isResettable
9
+ local _Level = TS.import(script, script.Parent.Parent, "Level")
10
+ local Level = _Level.Level
11
+ local Levels = _Level.Levels
12
+ local Logger = TS.import(script, script.Parent.Parent, "Logger").Logger
13
+ local runProcessor = TS.import(script, script.Parent.Parent, "Processor", "ProcessorInterface").runProcessor
14
+ --[[
15
+ *
16
+ * PHP: the `Closure(LogRecord|null, HandlerInterface): HandlerInterface`
17
+ * factory `FingersCrossedHandler` accepts in place of a handler.
18
+
19
+ ]]
20
+ --[[
21
+ *
22
+ * PHP: `Monolog\Handler\FingersCrossedHandler`.
23
+ *
24
+ * Buffers all records until a certain level is reached.
25
+ *
26
+ * The advantage of this approach is that you get no clutter in your log. Only
27
+ * sessions which actually trigger an error (or whatever the
28
+ * `activationStrategy` is) end up in the log, but they contain all records,
29
+ * not only those above the level threshold.
30
+ *
31
+ * There is a `passthruLevel` as well, which means that at the end, even if the
32
+ * handler never got activated, it will still send through log records of at
33
+ * least that level.
34
+ *
35
+ * Upstream gets its processor stack from `ProcessableHandlerTrait`; Luau has
36
+ * no traits, so the trait's members are spelled out here -- see the same note
37
+ * on `GroupHandler` and `AbstractProcessingHandler`.
38
+
39
+ ]]
40
+ local FingersCrossedHandler
41
+ do
42
+ local super = Handler
43
+ FingersCrossedHandler = setmetatable({}, {
44
+ __tostring = function()
45
+ return "FingersCrossedHandler"
46
+ end,
47
+ __index = super,
48
+ })
49
+ FingersCrossedHandler.__index = FingersCrossedHandler
50
+ function FingersCrossedHandler.new(...)
51
+ local self = setmetatable({}, FingersCrossedHandler)
52
+ return self:constructor(...) or self
53
+ end
54
+ function FingersCrossedHandler:constructor(handler, activationStrategy, bufferSize, bubble, stopBuffering, passthruLevel)
55
+ if bufferSize == nil then
56
+ bufferSize = 0
57
+ end
58
+ if bubble == nil then
59
+ bubble = true
60
+ end
61
+ if stopBuffering == nil then
62
+ stopBuffering = true
63
+ end
64
+ super.constructor(self)
65
+ self.processors = {}
66
+ self.buffering = true
67
+ self.buffer = {}
68
+ local strategy = activationStrategy
69
+ if strategy == nil then
70
+ strategy = ErrorLevelActivationStrategy.new(Level.Warning)
71
+ end
72
+ -- Convert a bare level activationStrategy to an object.
73
+ if not isActivationStrategy(strategy) then
74
+ strategy = ErrorLevelActivationStrategy.new(strategy)
75
+ end
76
+ self.handler = handler
77
+ self.activationStrategy = strategy
78
+ self.bufferSize = bufferSize
79
+ self.bubble = bubble
80
+ self.stopBuffering = stopBuffering
81
+ if passthruLevel ~= nil then
82
+ self.passthruLevel = Logger:toMonologLevel(passthruLevel)
83
+ end
84
+ end
85
+ function FingersCrossedHandler:isHandling(_record)
86
+ return true
87
+ end
88
+ function FingersCrossedHandler:activate()
89
+ if self.stopBuffering then
90
+ self.buffering = false
91
+ end
92
+ self:getHandler(self.buffer[#self.buffer]):handleBatch(self.buffer)
93
+ self.buffer = {}
94
+ end
95
+ function FingersCrossedHandler:handle(record)
96
+ local processed = record
97
+ if not (#self.processors == 0) then
98
+ processed = self:processRecord(processed)
99
+ end
100
+ if self.buffering then
101
+ local _buffer = self.buffer
102
+ local _processed = processed
103
+ table.insert(_buffer, _processed)
104
+ if self.bufferSize > 0 and #self.buffer > self.bufferSize then
105
+ table.remove(self.buffer, 1)
106
+ end
107
+ if self.activationStrategy:isHandlerActivated(processed) then
108
+ self:activate()
109
+ end
110
+ else
111
+ self:getHandler(processed):handle(processed)
112
+ end
113
+ return self.bubble == false
114
+ end
115
+ function FingersCrossedHandler:close()
116
+ self:flushBuffer()
117
+ self:getHandler():close()
118
+ end
119
+ function FingersCrossedHandler:reset()
120
+ self:flushBuffer()
121
+ self:resetProcessors()
122
+ local handler = self:getHandler()
123
+ if isResettable(handler) then
124
+ handler:reset()
125
+ end
126
+ end
127
+ function FingersCrossedHandler:clear()
128
+ self.buffer = {}
129
+ self:reset()
130
+ end
131
+ function FingersCrossedHandler:flushBuffer()
132
+ local passthruLevel = self.passthruLevel
133
+ if passthruLevel ~= nil then
134
+ local _exp = self.buffer
135
+ -- ▼ ReadonlyArray.filter ▼
136
+ local _newValue = {}
137
+ local _callback = function(record)
138
+ return Levels:includes(passthruLevel, record.level)
139
+ end
140
+ local _length = 0
141
+ for _k, _v in _exp do
142
+ if _callback(_v, _k - 1, _exp) == true then
143
+ _length += 1
144
+ _newValue[_length] = _v
145
+ end
146
+ end
147
+ -- ▲ ReadonlyArray.filter ▲
148
+ self.buffer = _newValue
149
+ if not (#self.buffer == 0) then
150
+ self:getHandler(self.buffer[#self.buffer]):handleBatch(self.buffer)
151
+ end
152
+ end
153
+ self.buffer = {}
154
+ self.buffering = true
155
+ end
156
+ function FingersCrossedHandler:getHandler(record)
157
+ local _handler = self.handler
158
+ if type(_handler) == "function" then
159
+ local produced = self.handler(record, self)
160
+ if not isHandler(produced) then
161
+ error("The factory Closure should return a HandlerInterface")
162
+ end
163
+ self.handler = produced
164
+ end
165
+ return self.handler
166
+ end
167
+ function FingersCrossedHandler:pushProcessor(processor)
168
+ local _processors = self.processors
169
+ local _processor = processor
170
+ table.insert(_processors, 1, _processor)
171
+ return self
172
+ end
173
+ function FingersCrossedHandler:popProcessor()
174
+ return table.remove(self.processors, 1)
175
+ end
176
+ function FingersCrossedHandler:processRecord(record)
177
+ local processed = record
178
+ for _, processor in self.processors do
179
+ processed = runProcessor(processor, processed)
180
+ end
181
+ return processed
182
+ end
183
+ function FingersCrossedHandler:resetProcessors()
184
+ for _, processor in self.processors do
185
+ if isResettable(processor) then
186
+ processor:reset()
187
+ end
188
+ end
189
+ end
190
+ function FingersCrossedHandler:setFormatter(formatter)
191
+ local handler = self:getHandler()
192
+ if isFormattable(handler) then
193
+ handler:setFormatter(formatter)
194
+ return self
195
+ end
196
+ return error(`The nested handler of type {tostring((getmetatable(handler)))} does not support formatters.`)
197
+ end
198
+ function FingersCrossedHandler:getFormatter()
199
+ local handler = self:getHandler()
200
+ if isFormattable(handler) then
201
+ return handler:getFormatter()
202
+ end
203
+ return error(`The nested handler of type {tostring((getmetatable(handler)))} does not support formatters.`)
204
+ end
205
+ end
206
+ return {
207
+ FingersCrossedHandler = FingersCrossedHandler,
208
+ }
@@ -0,0 +1,23 @@
1
+ import type { FormatterInterface } from "../Formatter/FormatterInterface";
2
+ import type { HandlerInterface } from "./HandlerInterface";
3
+ /**
4
+ * PHP: `Monolog\Handler\FormattableHandlerInterface`.
5
+ *
6
+ * Upstream pairs this with `FormattableHandlerTrait`; Luau has no traits, so
7
+ * implementers carry the `$formatter` field and `getDefaultFormatter()`
8
+ * themselves -- see `AbstractProcessingHandler`.
9
+ */
10
+ export interface FormattableHandlerInterface extends HandlerInterface {
11
+ /** Sets the formatter. */
12
+ setFormatter(formatter: FormatterInterface): this;
13
+ /** Gets the formatter. */
14
+ getFormatter(): FormatterInterface;
15
+ }
16
+ /**
17
+ * True when the given value implements `FormattableHandlerInterface`.
18
+ *
19
+ * PHP checks this with `instanceof`; there is no `instanceof` for an
20
+ * interface here, so this checks structurally for the two callable members,
21
+ * exactly as `isResettable()` (`ResettableInterface.ts`) already does.
22
+ */
23
+ export declare function isFormattable(value: unknown): value is FormattableHandlerInterface;
@@ -0,0 +1,36 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ --[[
3
+ *
4
+ * PHP: `Monolog\Handler\FormattableHandlerInterface`.
5
+ *
6
+ * Upstream pairs this with `FormattableHandlerTrait`; Luau has no traits, so
7
+ * implementers carry the `$formatter` field and `getDefaultFormatter()`
8
+ * themselves -- see `AbstractProcessingHandler`.
9
+
10
+ ]]
11
+ --[[
12
+ *
13
+ * True when the given value implements `FormattableHandlerInterface`.
14
+ *
15
+ * PHP checks this with `instanceof`; there is no `instanceof` for an
16
+ * interface here, so this checks structurally for the two callable members,
17
+ * exactly as `isResettable()` (`ResettableInterface.ts`) already does.
18
+
19
+ ]]
20
+ local function isFormattable(value)
21
+ local _value = value
22
+ if not (type(_value) == "table") then
23
+ return false
24
+ end
25
+ local candidate = value
26
+ local _setFormatter = candidate.setFormatter
27
+ local _condition = type(_setFormatter) == "function"
28
+ if _condition then
29
+ local _getFormatter = candidate.getFormatter
30
+ _condition = type(_getFormatter) == "function"
31
+ end
32
+ return _condition
33
+ end
34
+ return {
35
+ isFormattable = isFormattable,
36
+ }
@@ -0,0 +1,49 @@
1
+ import { Handler } from "./Handler";
2
+ import type { FormatterInterface } from "../Formatter/FormatterInterface";
3
+ import type { HandlerInterface } from "./HandlerInterface";
4
+ import type { LogRecord } from "../LogRecord";
5
+ import type { Processor } from "../Processor/ProcessorInterface";
6
+ import type { ProcessableHandlerInterface } from "./ProcessableHandlerInterface";
7
+ import type { ResettableInterface } from "../ResettableInterface";
8
+ /**
9
+ * PHP: `Monolog\Handler\GroupHandler`. Forwards records to multiple handlers.
10
+ *
11
+ * Upstream gets its processor stack from `ProcessableHandlerTrait`; Luau has
12
+ * no traits, so the trait's members (`processors`, `processRecord()`,
13
+ * `resetProcessors()`, `pushProcessor()`, `popProcessor()`) are spelled out
14
+ * here, the same way `AbstractProcessingHandler` already folds it in.
15
+ */
16
+ export declare class GroupHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface {
17
+ protected processors: Processor[];
18
+ protected handlers: Array<HandlerInterface>;
19
+ protected bubble: boolean;
20
+ /**
21
+ * Upstream's `\InvalidArgumentException` for a non-handler in the array is
22
+ * kept rather than dropped: it is `GroupHandler`'s own explicit check, not
23
+ * PHP's parameter type-check, and this port is consumed from plain Luau
24
+ * too, where TypeScript's `Array<HandlerInterface>` proves nothing. There
25
+ * is no exception class hierarchy on this platform, so it is a plain
26
+ * `error()` carrying upstream's message.
27
+ */
28
+ constructor(handlers: Array<HandlerInterface>, bubble?: boolean);
29
+ /** Checks whether any of the grouped handlers will handle the record. */
30
+ isHandling(record: LogRecord): boolean;
31
+ /** Handles a record, forwarding a clone of it to every grouped handler. */
32
+ handle(record: LogRecord): boolean;
33
+ /** Handles a set of records at once. */
34
+ handleBatch(records: Array<LogRecord>): void;
35
+ /** Adds a processor in the stack. */
36
+ pushProcessor(processor: Processor): this;
37
+ /** Removes the processor on top of the stack and returns it. */
38
+ popProcessor(): Processor | undefined;
39
+ /** Processes a record. */
40
+ protected processRecord(record: LogRecord): LogRecord;
41
+ /** Resets any processors on this handler that support resetting. */
42
+ protected resetProcessors(): void;
43
+ /** Resets this handler's processors and every grouped handler. */
44
+ reset(): void;
45
+ /** Closes every grouped handler. */
46
+ close(): void;
47
+ /** Sets the formatter on every grouped handler that supports one. */
48
+ setFormatter(formatter: FormatterInterface): this;
49
+ }
@@ -0,0 +1,139 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local Handler = TS.import(script, script.Parent, "Handler").Handler
4
+ local isFormattable = TS.import(script, script.Parent, "FormattableHandlerInterface").isFormattable
5
+ local isHandler = TS.import(script, script.Parent, "HandlerInterface").isHandler
6
+ local isResettable = TS.import(script, script.Parent.Parent, "ResettableInterface").isResettable
7
+ local runProcessor = TS.import(script, script.Parent.Parent, "Processor", "ProcessorInterface").runProcessor
8
+ --[[
9
+ *
10
+ * PHP: `Monolog\Handler\GroupHandler`. Forwards records to multiple handlers.
11
+ *
12
+ * Upstream gets its processor stack from `ProcessableHandlerTrait`; Luau has
13
+ * no traits, so the trait's members (`processors`, `processRecord()`,
14
+ * `resetProcessors()`, `pushProcessor()`, `popProcessor()`) are spelled out
15
+ * here, the same way `AbstractProcessingHandler` already folds it in.
16
+
17
+ ]]
18
+ local GroupHandler
19
+ do
20
+ local super = Handler
21
+ GroupHandler = setmetatable({}, {
22
+ __tostring = function()
23
+ return "GroupHandler"
24
+ end,
25
+ __index = super,
26
+ })
27
+ GroupHandler.__index = GroupHandler
28
+ function GroupHandler.new(...)
29
+ local self = setmetatable({}, GroupHandler)
30
+ return self:constructor(...) or self
31
+ end
32
+ function GroupHandler:constructor(handlers, bubble)
33
+ if bubble == nil then
34
+ bubble = true
35
+ end
36
+ super.constructor(self)
37
+ self.processors = {}
38
+ for _, handler in handlers do
39
+ if not isHandler(handler) then
40
+ error("The first argument of the GroupHandler must be an array of HandlerInterface instances.")
41
+ end
42
+ end
43
+ self.handlers = handlers
44
+ self.bubble = bubble
45
+ end
46
+ function GroupHandler:isHandling(record)
47
+ for _, handler in self.handlers do
48
+ if handler:isHandling(record) then
49
+ return true
50
+ end
51
+ end
52
+ return false
53
+ end
54
+ function GroupHandler:handle(record)
55
+ local processed = record
56
+ if not (#self.processors == 0) then
57
+ processed = self:processRecord(processed)
58
+ end
59
+ for _, handler in self.handlers do
60
+ handler:handle(processed:clone())
61
+ end
62
+ return self.bubble == false
63
+ end
64
+ function GroupHandler:handleBatch(records)
65
+ local batch = records
66
+ if not (#self.processors == 0) then
67
+ -- ▼ ReadonlyArray.map ▼
68
+ local _newValue = table.create(#records)
69
+ local _callback = function(record)
70
+ return self:processRecord(record)
71
+ end
72
+ for _k, _v in records do
73
+ _newValue[_k] = _callback(_v, _k - 1, records)
74
+ end
75
+ -- ▲ ReadonlyArray.map ▲
76
+ batch = _newValue
77
+ end
78
+ for _, handler in self.handlers do
79
+ -- ▼ ReadonlyArray.map ▼
80
+ local _newValue = table.create(#batch)
81
+ local _callback = function(record)
82
+ return record:clone()
83
+ end
84
+ for _k, _v in batch do
85
+ _newValue[_k] = _callback(_v, _k - 1, batch)
86
+ end
87
+ -- ▲ ReadonlyArray.map ▲
88
+ handler:handleBatch(_newValue)
89
+ end
90
+ end
91
+ function GroupHandler:pushProcessor(processor)
92
+ local _processors = self.processors
93
+ local _processor = processor
94
+ table.insert(_processors, 1, _processor)
95
+ return self
96
+ end
97
+ function GroupHandler:popProcessor()
98
+ return table.remove(self.processors, 1)
99
+ end
100
+ function GroupHandler:processRecord(record)
101
+ local processed = record
102
+ for _, processor in self.processors do
103
+ processed = runProcessor(processor, processed)
104
+ end
105
+ return processed
106
+ end
107
+ function GroupHandler:resetProcessors()
108
+ for _, processor in self.processors do
109
+ if isResettable(processor) then
110
+ processor:reset()
111
+ end
112
+ end
113
+ end
114
+ function GroupHandler:reset()
115
+ self:resetProcessors()
116
+ for _, handler in self.handlers do
117
+ if isResettable(handler) then
118
+ handler:reset()
119
+ end
120
+ end
121
+ end
122
+ function GroupHandler:close()
123
+ super.close(self)
124
+ for _, handler in self.handlers do
125
+ handler:close()
126
+ end
127
+ end
128
+ function GroupHandler:setFormatter(formatter)
129
+ for _, handler in self.handlers do
130
+ if isFormattable(handler) then
131
+ handler:setFormatter(formatter)
132
+ end
133
+ end
134
+ return self
135
+ end
136
+ end
137
+ return {
138
+ GroupHandler = GroupHandler,
139
+ }
@@ -10,3 +10,13 @@ export interface HandlerInterface {
10
10
  /** Closes the handler. */
11
11
  close(): void;
12
12
  }
13
+ /**
14
+ * True when the given value implements `HandlerInterface`.
15
+ *
16
+ * PHP checks this with `instanceof` -- `GroupHandler`'s constructor and
17
+ * `FingersCrossedHandler::getHandler()` both reject a non-handler at runtime.
18
+ * There is no `instanceof` for an interface here, so this checks structurally
19
+ * for the four callable members, as `isResettable()`
20
+ * (`ResettableInterface.ts`) already does for its own interface.
21
+ */
22
+ export declare function isHandler(value: unknown): value is HandlerInterface;
@@ -1,3 +1,38 @@
1
1
  -- Compiled with roblox-ts v3.0.0
2
2
  --* PHP: `Monolog\Handler\HandlerInterface`.
3
- return nil
3
+ --[[
4
+ *
5
+ * True when the given value implements `HandlerInterface`.
6
+ *
7
+ * PHP checks this with `instanceof` -- `GroupHandler`'s constructor and
8
+ * `FingersCrossedHandler::getHandler()` both reject a non-handler at runtime.
9
+ * There is no `instanceof` for an interface here, so this checks structurally
10
+ * for the four callable members, as `isResettable()`
11
+ * (`ResettableInterface.ts`) already does for its own interface.
12
+
13
+ ]]
14
+ local function isHandler(value)
15
+ local _value = value
16
+ if not (type(_value) == "table") then
17
+ return false
18
+ end
19
+ local candidate = value
20
+ local _isHandling = candidate.isHandling
21
+ local _condition = type(_isHandling) == "function"
22
+ if _condition then
23
+ local _handle = candidate.handle
24
+ _condition = type(_handle) == "function"
25
+ if _condition then
26
+ local _handleBatch = candidate.handleBatch
27
+ _condition = type(_handleBatch) == "function"
28
+ if _condition then
29
+ local _close = candidate.close
30
+ _condition = type(_close) == "function"
31
+ end
32
+ end
33
+ end
34
+ return _condition
35
+ end
36
+ return {
37
+ isHandler = isHandler,
38
+ }
@@ -0,0 +1,26 @@
1
+ import type { HandlerInterface } from "./HandlerInterface";
2
+ import type { Processor } from "../Processor/ProcessorInterface";
3
+ /**
4
+ * PHP: `Monolog\Handler\ProcessableHandlerInterface`.
5
+ *
6
+ * Upstream pairs this with `ProcessableHandlerTrait`, which carries the
7
+ * `$processors` stack and the `processRecord()`/`resetProcessors()` helpers.
8
+ * Luau has no traits, so every implementer in this port spells those members
9
+ * out itself -- `AbstractProcessingHandler`, `GroupHandler` and
10
+ * `FingersCrossedHandler` each carry their own copy, the same way
11
+ * `AbstractProcessingHandler` already folded the trait in before this
12
+ * interface existed.
13
+ */
14
+ export interface ProcessableHandlerInterface extends HandlerInterface {
15
+ /** Adds a processor in the stack. */
16
+ pushProcessor(processor: Processor): this;
17
+ /**
18
+ * Removes the processor on top of the stack and returns it.
19
+ *
20
+ * Upstream throws `\LogicException` when the stack is empty; every
21
+ * pop-style method in this port (`Logger.popHandler()`,
22
+ * `AbstractProcessingHandler.popProcessor()`) returns `undefined` instead,
23
+ * and this signature follows them rather than upstream.
24
+ */
25
+ popProcessor(): Processor | undefined;
26
+ }
@@ -0,0 +1,15 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ --[[
3
+ *
4
+ * PHP: `Monolog\Handler\ProcessableHandlerInterface`.
5
+ *
6
+ * Upstream pairs this with `ProcessableHandlerTrait`, which carries the
7
+ * `$processors` stack and the `processRecord()`/`resetProcessors()` helpers.
8
+ * Luau has no traits, so every implementer in this port spells those members
9
+ * out itself -- `AbstractProcessingHandler`, `GroupHandler` and
10
+ * `FingersCrossedHandler` each carry their own copy, the same way
11
+ * `AbstractProcessingHandler` already folded the trait in before this
12
+ * interface existed.
13
+
14
+ ]]
15
+ return nil
@@ -1,55 +1,28 @@
1
1
  import { AbstractProcessingHandler } from "./AbstractProcessingHandler";
2
2
  import { Level } from "../Level";
3
- import type { FormatterInterface } from "../Formatter/FormatterInterface";
4
3
  import type { LogRecord } from "../LogRecord";
5
4
  /**
6
- * PHP: `Monolog\Handler\PHPConsoleHandler`.
5
+ * Writes formatted records to the Roblox output console.
7
6
  *
8
- * Upstream streams debug/error/exception data to the (now abandoned) "PHP
9
- * Console" Chrome extension via `PhpConsole\Connector`. There is no such
10
- * target on Roblox, so this adapts the same handler shape -- the `options`
11
- * bag, the record-kind branching in `write()`, the default formatter -- to
12
- * the platform's own output console (`print`/`warn`) instead, superseding
13
- * what the old, not-a-real-upstream-class `ConsoleHandler` used to do.
14
- */
15
- /**
16
- * Reduced from upstream's `Options`: everything else there
17
- * (`classesPartialsTraceIgnore`, `useOwnErrorsHandler`/`useOwnExceptionsHandler`,
18
- * `sourcesBasePath`, `registerHelper`, `serverEncoding`, `headersLimit`,
19
- * `password`, `enableSslOnlyMode`, `ipMasks`, `enableEvalListener`, every
20
- * `dumper*` key, `detectDumpTraceAndSource`, `dataStorage`) is either specific
21
- * to the PHP Console wire protocol or paired with `ErrorHandler`/`SignalHandler`,
22
- * both out of scope for this port -- there is no connector object left to
23
- * configure with them.
7
+ * No upstream ancestor: this is an original class for this platform.
8
+ *
9
+ * By shape it is this platform's `Monolog\Handler\StreamHandler`: hand
10
+ * `record.formatted` to the sink and nothing more. Roblox's console is that
11
+ * sink, and none of `StreamHandler`'s own API survives the swap -- there is
12
+ * no stream to open, no url/resource to accept, no file permissions, no
13
+ * `close()`, no buffering to flush. What is left is the level/bubble pair
14
+ * every handler has, plus the one choice the console does offer: records
15
+ * below `Level.Warning` go out through `print` (`MessageOutput`), the rest
16
+ * through `warn` (`MessageWarning`), so Studio colors them accordingly and
17
+ * `LogService` reports the right `Enum.MessageType`. `error` is deliberately
18
+ * not used for higher levels -- on Luau it throws and unwinds the caller
19
+ * instead of merely writing a line.
20
+ *
21
+ * The formatter is respected, unlike the PHP Console port that preceded this
22
+ * one: it is what turns the record into the printed text. The default is
23
+ * `AbstractProcessingHandler`'s own `LineFormatter`.
24
24
  */
25
- export interface Options {
26
- /** Whether this handler is active at all. */
27
- enabled: boolean;
28
- /** Context keys checked, in order, for a debug "tag" to prefix the message with. */
29
- debugTagsKeysInContext: Array<string>;
30
- }
31
25
  export declare class RobloxConsoleHandler extends AbstractProcessingHandler {
32
- private readonly options;
33
- constructor(options?: Partial<Options>, level?: Level, bubble?: boolean);
34
- /** PHP: `PHPConsoleHandler::getOptions()`. */
35
- getOptions(): Options;
36
- /** PHP: `PHPConsoleHandler::handle()`. */
37
- handle(record: LogRecord): boolean;
38
- /** PHP: `PHPConsoleHandler::write()`. */
26
+ constructor(level?: Level, bubble?: boolean);
39
27
  protected write(record: LogRecord): void;
40
- /** PHP: `PHPConsoleHandler::handleDebugRecord()`. */
41
- private writeDebugRecord;
42
- /** PHP: `PHPConsoleHandler::handleExceptionRecord()`. */
43
- private writeExceptionRecord;
44
- /** PHP: `PHPConsoleHandler::handleErrorRecord()`. */
45
- private writeErrorRecord;
46
- /**
47
- * PHP: `PHPConsoleHandler::getRecordTags()`. Upstream also checks
48
- * `$filteredContext[0]`, PHP's implicit first-array-index -- Luau/TS have
49
- * no positional index on a `RecordBag`, so only the named
50
- * `debugTagsKeysInContext` keys are checked.
51
- */
52
- private getRecordTags;
53
- /** PHP: `PHPConsoleHandler::getDefaultFormatter()`. */
54
- protected getDefaultFormatter(): FormatterInterface;
55
28
  }
@@ -4,36 +4,29 @@ local AbstractProcessingHandler = TS.import(script, script.Parent, "AbstractProc
4
4
  local _Level = TS.import(script, script.Parent.Parent, "Level")
5
5
  local Level = _Level.Level
6
6
  local Levels = _Level.Levels
7
- local LineFormatter = TS.import(script, script.Parent.Parent, "Formatter", "LineFormatter").LineFormatter
8
- local Utils = TS.import(script, script.Parent.Parent, "Utils").Utils
9
7
  --[[
10
8
  *
11
- * PHP: `Monolog\Handler\PHPConsoleHandler`.
9
+ * Writes formatted records to the Roblox output console.
12
10
  *
13
- * Upstream streams debug/error/exception data to the (now abandoned) "PHP
14
- * Console" Chrome extension via `PhpConsole\Connector`. There is no such
15
- * target on Roblox, so this adapts the same handler shape -- the `options`
16
- * bag, the record-kind branching in `write()`, the default formatter -- to
17
- * the platform's own output console (`print`/`warn`) instead, superseding
18
- * what the old, not-a-real-upstream-class `ConsoleHandler` used to do.
19
-
20
- ]]
21
- --[[
22
- *
23
- * Reduced from upstream's `Options`: everything else there
24
- * (`classesPartialsTraceIgnore`, `useOwnErrorsHandler`/`useOwnExceptionsHandler`,
25
- * `sourcesBasePath`, `registerHelper`, `serverEncoding`, `headersLimit`,
26
- * `password`, `enableSslOnlyMode`, `ipMasks`, `enableEvalListener`, every
27
- * `dumper*` key, `detectDumpTraceAndSource`, `dataStorage`) is either specific
28
- * to the PHP Console wire protocol or paired with `ErrorHandler`/`SignalHandler`,
29
- * both out of scope for this port -- there is no connector object left to
30
- * configure with them.
11
+ * No upstream ancestor: this is an original class for this platform.
12
+ *
13
+ * By shape it is this platform's `Monolog\Handler\StreamHandler`: hand
14
+ * `record.formatted` to the sink and nothing more. Roblox's console is that
15
+ * sink, and none of `StreamHandler`'s own API survives the swap -- there is
16
+ * no stream to open, no url/resource to accept, no file permissions, no
17
+ * `close()`, no buffering to flush. What is left is the level/bubble pair
18
+ * every handler has, plus the one choice the console does offer: records
19
+ * below `Level.Warning` go out through `print` (`MessageOutput`), the rest
20
+ * through `warn` (`MessageWarning`), so Studio colors them accordingly and
21
+ * `LogService` reports the right `Enum.MessageType`. `error` is deliberately
22
+ * not used for higher levels -- on Luau it throws and unwinds the caller
23
+ * instead of merely writing a line.
24
+ *
25
+ * The formatter is respected, unlike the PHP Console port that preceded this
26
+ * one: it is what turns the record into the printed text. The default is
27
+ * `AbstractProcessingHandler`'s own `LineFormatter`.
31
28
 
32
29
  ]]
33
- local DEFAULT_OPTIONS = {
34
- enabled = true,
35
- debugTagsKeysInContext = { "tag" },
36
- }
37
30
  local RobloxConsoleHandler
38
31
  do
39
32
  local super = AbstractProcessingHandler
@@ -48,10 +41,7 @@ do
48
41
  local self = setmetatable({}, RobloxConsoleHandler)
49
42
  return self:constructor(...) or self
50
43
  end
51
- function RobloxConsoleHandler:constructor(options, level, bubble)
52
- if options == nil then
53
- options = {}
54
- end
44
+ function RobloxConsoleHandler:constructor(level, bubble)
55
45
  if level == nil then
56
46
  level = Level.Debug
57
47
  end
@@ -59,81 +49,20 @@ do
59
49
  bubble = true
60
50
  end
61
51
  super.constructor(self, level, bubble)
62
- local _object = {}
63
- local _left = "enabled"
64
- local _condition = options.enabled
65
- if _condition == nil then
66
- _condition = DEFAULT_OPTIONS.enabled
67
- end
68
- _object[_left] = _condition
69
- _object.debugTagsKeysInContext = options.debugTagsKeysInContext or DEFAULT_OPTIONS.debugTagsKeysInContext
70
- self.options = _object
71
- end
72
- function RobloxConsoleHandler:getOptions()
73
- return self.options
74
- end
75
- function RobloxConsoleHandler:handle(record)
76
- if self.options.enabled then
77
- return super.handle(self, record)
78
- end
79
- -- Upstream also requires `connector.isActiveClient()` here; there is no
80
- -- connector to ask, so `enabled` is the only gate left.
81
- return not self.bubble
82
52
  end
83
53
  function RobloxConsoleHandler:write(record)
84
- if Levels:isLowerThan(record.level, Level.Notice) then
85
- self:writeDebugRecord(record)
86
- elseif record.context.exception ~= nil then
87
- self:writeExceptionRecord(record)
88
- else
89
- self:writeErrorRecord(record)
90
- end
91
- end
92
- function RobloxConsoleHandler:writeDebugRecord(record)
93
- local _binding = self:getRecordTags(record)
94
- local tags = _binding[1]
95
- local filteredContext = _binding[2]
96
- local message = record.message
97
- if (next(filteredContext)) ~= nil then
98
- message = `{message} {Utils:jsonEncode(filteredContext)}`
54
+ -- `handle()` always fills `formatted` in before calling `write()`;
55
+ -- the fallback is for a direct call that did not go through it.
56
+ local _condition = record.formatted
57
+ if _condition == nil then
58
+ _condition = record.message
99
59
  end
100
- print(`[{tags}] {message}`)
101
- end
102
- function RobloxConsoleHandler:writeExceptionRecord(record)
103
- warn(tostring(record.context.exception))
104
- end
105
- function RobloxConsoleHandler:writeErrorRecord(record)
106
- local context = record.context
107
- local message = if context.message ~= nil then tostring(context.message) else record.message
108
- if context.file == nil and context.line == nil then
109
- warn(message)
60
+ local message = _condition
61
+ if Levels:isLowerThan(record.level, Level.Warning) then
62
+ print(message)
110
63
  return nil
111
64
  end
112
- local file = if context.file ~= nil then tostring(context.file) else "?"
113
- local line = if context.line ~= nil then tostring(context.line) else "?"
114
- warn(`{message} ({file}:{line})`)
115
- end
116
- function RobloxConsoleHandler:getRecordTags(record)
117
- local filteredContext = {}
118
- for key, value in pairs(record.context) do
119
- filteredContext[key] = value
120
- end
121
- local tag
122
- for _, key in self.options.debugTagsKeysInContext do
123
- if filteredContext[key] ~= nil then
124
- tag = tostring(filteredContext[key])
125
- filteredContext[key] = nil
126
- break
127
- end
128
- end
129
- local _condition = tag
130
- if _condition == nil then
131
- _condition = Levels:toPsrLogLevel(record.level)
132
- end
133
- return { _condition, filteredContext }
134
- end
135
- function RobloxConsoleHandler:getDefaultFormatter()
136
- return LineFormatter.new("%message%")
65
+ warn(message)
137
66
  end
138
67
  end
139
68
  return {
@@ -0,0 +1,26 @@
1
+ import { GroupHandler } from "./GroupHandler";
2
+ import type { LogRecord } from "../LogRecord";
3
+ /**
4
+ * PHP: `Monolog\Handler\WhatFailureGroupHandler`.
5
+ *
6
+ * Forwards records to multiple handlers, suppressing failures of each handler
7
+ * and continuing through to give every handler a chance to succeed.
8
+ *
9
+ * Upstream wraps each call in `try { ... } catch (Throwable) {}`. Luau's
10
+ * error mechanism is `error()`/`pcall()`, not exceptions, so each call goes
11
+ * through `pcall()` and its failure result is discarded -- same "what
12
+ * failure?" semantics, same swallow-everything breadth.
13
+ */
14
+ export declare class WhatFailureGroupHandler extends GroupHandler {
15
+ /** Handles a record, letting every grouped handler fail independently. */
16
+ handle(record: LogRecord): boolean;
17
+ /** Handles a set of records at once, ignoring any handler that fails. */
18
+ handleBatch(records: Array<LogRecord>): void;
19
+ /**
20
+ * Closes every grouped handler, ignoring any that fails.
21
+ *
22
+ * Upstream deliberately does not call `parent::close()` here (unlike
23
+ * `GroupHandler::close()`), so neither does this.
24
+ */
25
+ close(): void;
26
+ }
@@ -0,0 +1,85 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local GroupHandler = TS.import(script, script.Parent, "GroupHandler").GroupHandler
4
+ --[[
5
+ *
6
+ * PHP: `Monolog\Handler\WhatFailureGroupHandler`.
7
+ *
8
+ * Forwards records to multiple handlers, suppressing failures of each handler
9
+ * and continuing through to give every handler a chance to succeed.
10
+ *
11
+ * Upstream wraps each call in `try { ... } catch (Throwable) {}`. Luau's
12
+ * error mechanism is `error()`/`pcall()`, not exceptions, so each call goes
13
+ * through `pcall()` and its failure result is discarded -- same "what
14
+ * failure?" semantics, same swallow-everything breadth.
15
+
16
+ ]]
17
+ local WhatFailureGroupHandler
18
+ do
19
+ local super = GroupHandler
20
+ WhatFailureGroupHandler = setmetatable({}, {
21
+ __tostring = function()
22
+ return "WhatFailureGroupHandler"
23
+ end,
24
+ __index = super,
25
+ })
26
+ WhatFailureGroupHandler.__index = WhatFailureGroupHandler
27
+ function WhatFailureGroupHandler.new(...)
28
+ local self = setmetatable({}, WhatFailureGroupHandler)
29
+ return self:constructor(...) or self
30
+ end
31
+ function WhatFailureGroupHandler:constructor(...)
32
+ super.constructor(self, ...)
33
+ end
34
+ function WhatFailureGroupHandler:handle(record)
35
+ local processed = record
36
+ if not (#self.processors == 0) then
37
+ processed = self:processRecord(processed)
38
+ end
39
+ for _, handler in self.handlers do
40
+ pcall(function()
41
+ return handler:handle(processed:clone())
42
+ end)
43
+ end
44
+ return self.bubble == false
45
+ end
46
+ function WhatFailureGroupHandler:handleBatch(records)
47
+ local batch = records
48
+ if not (#self.processors == 0) then
49
+ -- ▼ ReadonlyArray.map ▼
50
+ local _newValue = table.create(#records)
51
+ local _callback = function(record)
52
+ return self:processRecord(record)
53
+ end
54
+ for _k, _v in records do
55
+ _newValue[_k] = _callback(_v, _k - 1, records)
56
+ end
57
+ -- ▲ ReadonlyArray.map ▲
58
+ batch = _newValue
59
+ end
60
+ for _, handler in self.handlers do
61
+ pcall(function()
62
+ -- ▼ ReadonlyArray.map ▼
63
+ local _newValue = table.create(#batch)
64
+ local _callback = function(record)
65
+ return record:clone()
66
+ end
67
+ for _k, _v in batch do
68
+ _newValue[_k] = _callback(_v, _k - 1, batch)
69
+ end
70
+ -- ▲ ReadonlyArray.map ▲
71
+ return handler:handleBatch(_newValue)
72
+ end)
73
+ end
74
+ end
75
+ function WhatFailureGroupHandler:close()
76
+ for _, handler in self.handlers do
77
+ pcall(function()
78
+ return handler:close()
79
+ end)
80
+ end
81
+ end
82
+ end
83
+ return {
84
+ WhatFailureGroupHandler = WhatFailureGroupHandler,
85
+ }
package/package.json CHANGED
@@ -1,10 +1,15 @@
1
1
  {
2
2
  "$schema": "https://www.schemastore.org/package.json",
3
3
  "name": "@larablox/monolog",
4
- "version": "0.1.1",
4
+ "version": "0.2.0",
5
5
  "description": "The Larablox port of Monolog.",
6
6
  "license": "MIT",
7
- "keywords": ["monolog", "logging", "larablox", "roblox-ts"],
7
+ "keywords": [
8
+ "monolog",
9
+ "logging",
10
+ "larablox",
11
+ "roblox-ts"
12
+ ],
8
13
  "homepage": "https://github.com/larablox/monolog",
9
14
  "repository": {
10
15
  "type": "git",
@@ -16,7 +21,10 @@
16
21
  "publishConfig": {
17
22
  "access": "public"
18
23
  },
19
- "files": ["out/Monolog", "out/index.d.ts"],
24
+ "files": [
25
+ "out/Monolog",
26
+ "out/index.d.ts"
27
+ ],
20
28
  "types": "out/index.d.ts",
21
29
  "type": "module",
22
30
  "scripts": {