From 25120b07ef03fcdcc51924d71163f09b22ef820e Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 7 Sep 2024 20:56:17 -0700 Subject: [PATCH 01/53] recovered from Add New Logging Features: Syslog and Restful Support #1335 --- docs/Getting-Started/Migrating/0X-to-1X.md | 2 +- docs/Tutorials/Configuration.md | 2 + docs/Tutorials/Logging/Methods/Custom.md | 63 +- docs/Tutorials/Logging/Methods/File.md | 39 + docs/Tutorials/Logging/Methods/Syslog.md | 56 + docs/Tutorials/Logging/Overview.md | 61 +- docs/Tutorials/Logging/Types/General.md | 70 ++ docs/Tutorials/Logging/Types/Requests.md | 24 +- docs/Tutorials/Logging/Types/Trace.md | 63 + examples/Logging.ps1 | 82 +- examples/server.psd1 | 1 + src/Listener/PodeContext.cs | 18 +- src/Listener/PodeFileWatcher.cs | 2 +- src/Listener/PodeHelpers.cs | 52 +- src/Listener/PodeListener.cs | 16 +- src/Listener/PodeLogger.cs | 176 +++ src/Listener/PodeReceiver.cs | 8 +- src/Listener/PodeRequest.cs | 8 +- src/Listener/PodeResponse.cs | 16 +- src/Listener/PodeResponseHeaders.cs | 9 +- src/Listener/PodeSignalRequest.cs | 7 +- src/Listener/PodeSmtpRequest.cs | 2 +- src/Listener/PodeSocket.cs | 20 +- src/Listener/PodeTcpRequest.cs | 2 +- src/Listener/PodeWatcher.cs | 8 +- src/Listener/PodeWebSocket.cs | 10 +- src/Locales/ar/Pode.psd1 | 8 +- src/Locales/de/Pode.psd1 | 6 +- src/Locales/en-us/Pode.psd1 | 6 +- src/Locales/en/Pode.psd1 | 9 +- src/Locales/es/Pode.psd1 | 6 +- src/Locales/fr/Pode.psd1 | 9 +- src/Locales/it/Pode.psd1 | 6 +- src/Locales/ja/Pode.psd1 | 9 +- src/Locales/ko/Pode.psd1 | 8 +- src/Locales/nl/Pode.psd1 | 9 +- src/Locales/pl/Pode.psd1 | 9 +- src/Locales/pt/Pode.psd1 | 6 +- src/Locales/zh/Pode.psd1 | 6 +- src/Pode.psd1 | 12 +- src/Private/Context.ps1 | 91 +- src/Private/Helpers.ps1 | 117 +- src/Private/Logging.ps1 | 1220 +++++++++++++++--- src/Private/Server.ps1 | 18 +- src/Public/Access.ps1 | 15 + src/Public/Authentication.ps1 | 27 + src/Public/Caching.ps1 | 21 + src/Public/Core.ps1 | 13 + src/Public/EndWare.ps1 | 68 + src/Public/Events.ps1 | 3 + src/Public/FileWatchers.ps1 | 13 +- src/Public/Flash.ps1 | 9 + src/Public/Handlers.ps1 | 11 +- src/Public/Headers.ps1 | 3 + src/Public/Logging.ps1 | 1305 ++++++++++++++++---- src/Public/Middleware.ps1 | 21 + src/Public/OAComponents.ps1 | 28 +- src/Public/OpenApi.ps1 | 33 + src/Public/Responses.ps1 | 6 + src/Public/Routes.ps1 | 42 + src/Public/SSE.ps1 | 6 + src/Public/Schedules.ps1 | 12 + src/Public/ScopedVariables.ps1 | 10 + src/Public/Secrets.ps1 | 3 + src/Public/Security.ps1 | 9 + src/Public/Sessions.ps1 | 3 + src/Public/State.ps1 | 6 + src/Public/Tasks.ps1 | 13 + src/Public/Threading.ps1 | 18 + src/Public/Timers.ps1 | 11 +- src/Public/Utilities.ps1 | 69 +- src/Public/Verbs.ps1 | 9 + src/Public/WebSockets.ps1 | 6 + tests/unit/Authentication.Tests.ps1 | 3 + tests/unit/Context.Tests.ps1 | 3 + tests/unit/Cookies.Tests.ps1 | 3 + tests/unit/CronParser.Tests.ps1 | 3 + tests/unit/Cryptography.Tests.ps1 | 3 + tests/unit/Endware.Tests.ps1 | 3 + tests/unit/Flash.Tests.ps1 | 3 + tests/unit/Handlers.Tests.ps1 | 3 + tests/unit/Headers.Tests.ps1 | 3 + tests/unit/Helpers.Tests.ps1 | 13 +- tests/unit/Logging.Tests.ps1 | 77 +- tests/unit/Mappers.Tests.ps1 | 3 + tests/unit/Metrics.Tests.ps1 | 3 + tests/unit/Middleware.Tests.ps1 | 3 + tests/unit/NameGenerator.Tests.ps1 | 3 + tests/unit/OpenApi.Tests.ps1 | 3 + tests/unit/PrivateOpenApi.Tests.ps1 | 3 + tests/unit/Responses.Tests.ps1 | 3 + tests/unit/Routes.Tests.ps1 | 3 + tests/unit/Schedules.Tests.ps1 | 3 + tests/unit/Security.Tests.ps1 | 3 + tests/unit/Server.Tests.ps1 | 16 +- tests/unit/Serverless.Tests.ps1 | 4 + tests/unit/Sessions.Tests.ps1 | 3 + tests/unit/State.Tests.ps1 | 3 + tests/unit/Timers.Tests.ps1 | 3 + 99 files changed, 3645 insertions(+), 696 deletions(-) create mode 100644 docs/Tutorials/Logging/Methods/Syslog.md create mode 100644 docs/Tutorials/Logging/Types/General.md create mode 100644 docs/Tutorials/Logging/Types/Trace.md create mode 100644 src/Listener/PodeLogger.cs create mode 100644 src/Public/EndWare.ps1 diff --git a/docs/Getting-Started/Migrating/0X-to-1X.md b/docs/Getting-Started/Migrating/0X-to-1X.md index 8760d6d78..be6423609 100644 --- a/docs/Getting-Started/Migrating/0X-to-1X.md +++ b/docs/Getting-Started/Migrating/0X-to-1X.md @@ -154,7 +154,7 @@ Request and Error logging are inbuilt logging types that can be enabled using [` | [`Disable-PodeRequestLogging`](../../../Functions/Logging/Disable-PodeRequestLogging) | | [`Disable-PodeErrorLogging`](../../../Functions/Logging/Disable-PodeErrorLogging) | | [`Remove-PodeLogger`](../../../Functions/Logging/Remove-PodeLogger) | -| [`Clear-PodeLoggers`](../../../Functions/Logging/Clear-PodeLoggers) | +| [`Clear-PodeLogger`](../../../Functions/Logging/Clear-PodeLogger) | ### Writing Logs diff --git a/docs/Tutorials/Configuration.md b/docs/Tutorials/Configuration.md index 6b18e04fa..cec85bc93 100644 --- a/docs/Tutorials/Configuration.md +++ b/docs/Tutorials/Configuration.md @@ -79,6 +79,8 @@ A "path" like `Server.Ssl.Protocols` looks like the below in the file: | Server.FileMonitor | Defines configuration for restarting the server based on file updates | [link](../Restarting/Types/FileMonitoring) | | Server.ReceiveTimeout | Define the amount of time a Receive method call will block waiting for data | [link](../Endpoints/Basic/StaticContent/#server-timeout) | | Server.DefaultFolders | Set the Default Folders paths | [link](../Routes/Utilities/StaticContent/#changing-the-default-folders) | +| Server.Logging.QueueLimit | Set the maximum number of logs allowed in the queue | [link](../Logging/Overview) | +| Server.Logging.Masking.Patterns | Regular expressions congiguration to mask sensitive logs information | [link](../Logging/Overview) | | Web.OpenApi.DefaultDefinitionTag | Define the primary tag name for OpenAPI ( `default` is the default) | [link](../OpenAPI/Overview) | | Web.OpenApi.UsePodeYamlInternal | Force the use of the internal YAML converter (`False` is the default) | | | Web.Static.ValidateLast | Changes the way routes are processed. | [link](../Routes/Utilities/StaticContent) | diff --git a/docs/Tutorials/Logging/Methods/Custom.md b/docs/Tutorials/Logging/Methods/Custom.md index 8fe03a7b1..a6cde3cc4 100644 --- a/docs/Tutorials/Logging/Methods/Custom.md +++ b/docs/Tutorials/Logging/Methods/Custom.md @@ -1,35 +1,76 @@ # Custom -Sometimes you don't want to log to a file, or the terminal; instead you want to log to something better, like LogStash, Splunk, Athena, or any other central logging platform. Although Pode doesn't have these inbuilt (yet!) it is possible to create a custom logging method, where you define a ScriptBlock with logic to send logs to these platforms. +Sometimes you may want to log to platforms other than a file or the terminal, such as LogStash, Splunk, Athena, or other central logging platforms. Although Pode doesn't have these integrations built-in (yet!), it is possible to create a custom logging method by defining a ScriptBlock with the logic to send logs to these platforms. -These custom method can be used for any log type - Requests, Error, or Custom. +Custom methods can be used for any log type: Requests, Error, or Custom. -The ScriptBlock you create will be supplied two arguments: +The ScriptBlock you create will receive two arguments: -1. The item to be logged. This could be a string (from Requests/Errors), or any custom type. -2. The options you supplied on [`New-PodeLoggingMethod`](../../../../Functions/Logging/New-PodeLoggingMethod). +1. The item to be logged. This could be a string (from Requests/Errors) or any custom type. + +2. The options you supplied to [`New-PodeLoggingMethod`](../../../../Functions/Logging/New-PodeLoggingMethod). + +Additionally, custom logging methods can be run in their own runspace by using the `-UseRunspace` parameter, ensuring isolation and efficiency. ## Examples ### Send to S3 Bucket -This example will take whatever item is supplied to it, convert it to a string, and then send it off to some S3 bucket in AWS. In this case, it will be logging Requests: +This example takes the supplied item, converts it to a string, and sends it to an S3 bucket in AWS. In this case, it will log Requests: +#### Legacy (No Runspace) ```powershell $s3_options = @{ AccessKey = $AccessKey SecretKey = $SecretKey } -$s3_logging = New-PodeLoggingType -Custom -ArgumentList $s3_options -ScriptBlock { +$s3_logging = New-PodeLoggingMethod -Custom -ArgumentList $s3_options -ScriptBlock { param($item, $s3_opts) - Write-S3Object ` - -BucketName '' ` - -Content $item.ToString() ` - -AccessKey $s3_opts.AccessKey ` + Write-S3Object \` + -BucketName '' \` + -Content $item.ToString() \` + -AccessKey $s3_opts.AccessKey \` -SecretKey $s3_opts.SecretKey } +$s3_logging | Enable-PodeRequestLogging +``` + +#### With Runspace + +```powershell +$s3_options = @{ + AccessKey = $AccessKey + SecretKey = $SecretKey +} + +$s3_logging = New-PodeLoggingMethod -Custom -UseRunspace -CustomOptions $s3_options -ScriptBlock { + # No param() allowed here + Write-S3Object \` + -BucketName '' \` + -Content $Item.ToString() \` + -AccessKey $Options.AccessKey \` + -SecretKey $Options.SecretKey +} $s3_logging | Enable-PodeRequestLogging ``` + + +In this example, the `-UseRunspace` parameter ensures that the custom logging method runs in its own runspace, providing better isolation and performance. + +##### Variable available inside the ScriptBlock + +| Variable | Type | Description | +| ----------------------- | ----------------------------- | ------------------------------------------------ | +| Item | string | Log message content | +| Options | hashtable | The options supplied to the logging method | +| Options.FailureAction | string (Ignore, Report, Halt) | Defines the behavior in case of failure. | +| Options.DataFormat | string | The date format to use for the log entries. | +| Options.AsUTC | boolean | the time is logged in UTC instead of local time. | +| Options. | PSObject | Any key passed using `-CustomOptions` parameter | +| RawItem | hashtable | Log message in raw format | + + +By leveraging custom logging methods, you can extend Pode's logging capabilities to integrate with a wide range of external platforms, providing flexibility and control over your logging strategy. \ No newline at end of file diff --git a/docs/Tutorials/Logging/Methods/File.md b/docs/Tutorials/Logging/Methods/File.md index 84b28df21..946923a79 100644 --- a/docs/Tutorials/Logging/Methods/File.md +++ b/docs/Tutorials/Logging/Methods/File.md @@ -39,3 +39,42 @@ By default Pode puts all logs in the `./logs` directory. You can use a custom pa ```powershell New-PodeLoggingMethod -File -Name 'requests' -Path 'E:/logs' | Enable-PodeRequestLogging ``` + +### Format + +The Format parameter allows you to specify the format of the log entries. Available options are: + +- RFC3164 +- RFC5424 +- Simple +- Default (default option) + +The Simple format uses the following structure: timestamp level source message. The Default format uses the legacy Pode format. + +```powershell +New-PodeLoggingMethod -File -Name 'requests' -Format 'Simple' | Enable-PodeRequestLogging +``` +A log entry using the Simple format might look like this: + +```arduino +2024-08-01T12:00:00Z INFO MyApp "Request received" +``` + +### Custom Separator +When using the Simple format, you can specify a custom separator for log entries: + +```powershell +New-PodeLoggingMethod -File -Name 'requests' -Format 'Simple' -Separator ',' | Enable-PodeRequestLogging +``` + +A log entry using the Simple format with a comma separator might look like this: +```arduino +2024-08-01T12:00:00Z,INFO,MyApp,"Request received" +``` + +### Maximum Log Entry Length +The MaxLength parameter sets the maximum length of log entries. The default value is -1, which means no limit. + +```powershell +New-PodeLoggingMethod -File -Name 'requests' -MaxLength 500 | Enable-PodeRequestLogging +``` \ No newline at end of file diff --git a/docs/Tutorials/Logging/Methods/Syslog.md b/docs/Tutorials/Logging/Methods/Syslog.md new file mode 100644 index 000000000..eba6db8d2 --- /dev/null +++ b/docs/Tutorials/Logging/Methods/Syslog.md @@ -0,0 +1,56 @@ + +# Syslog + +Pode supports logging items to a Syslog server using the inbuilt Syslog logging method. This method allows you to define various parameters such as the Syslog server address, port, transport protocol, and more. The logging method will convert any item to a string and send it to the configured Syslog server. + +By default, Pode will use UDP as the transport protocol and RFC5424 as the Syslog protocol. You can customize these settings based on your Syslog server requirements. + +## Examples + +### Basic + +The following example will setup the Syslog logging method for logging requests: + +```powershell +New-PodeLoggingMethod -Syslog -Server '192.168.1.1' | Enable-PodeRequestLogging +``` + +### Custom Port + +The following example will configure Syslog logging to use a custom port. The default port is 514, but you can specify a different port if needed: + +```powershell +New-PodeLoggingMethod -Syslog -Server '192.168.1.1' -Port 1514 | Enable-PodeRequestLogging +``` + +### Secure Connection with TLS + +The following example will configure Syslog logging to use TLS for a secure connection. You can also specify the TLS protocol version to use: + +```powershell +New-PodeLoggingMethod -Syslog -Server '192.168.1.1' -Transport 'TLS' -TlsProtocol 'TLS1.2' | Enable-PodeRequestLogging +``` + +### Custom Syslog Protocol + +The following example will configure Syslog logging to use a different Syslog protocol. The default protocol is RFC5424, but you can specify RFC3164 if needed: + +```powershell +New-PodeLoggingMethod -Syslog -Server '192.168.1.1' -SyslogProtocol 'RFC3164' | Enable-PodeRequestLogging +``` + +### Skip Certificate Validation + +The following example will configure Syslog logging to skip certificate validation for TLS connections. This is useful for testing purposes but not recommended for production environments: + +```powershell +New-PodeLoggingMethod -Syslog -Server '192.168.1.1' -Transport 'TLS' -SkipCertificateCheck | Enable-PodeRequestLogging +``` + +### Custom Encoding + +The following example will configure Syslog logging to use a different encoding for the Syslog messages. The default encoding is UTF8: + +```powershell +New-PodeLoggingMethod -Syslog -Server '192.168.1.1' -Encoding 'ASCII' | Enable-PodeRequestLogging +``` \ No newline at end of file diff --git a/docs/Tutorials/Logging/Overview.md b/docs/Tutorials/Logging/Overview.md index ac6527c05..36fb528dd 100644 --- a/docs/Tutorials/Logging/Overview.md +++ b/docs/Tutorials/Logging/Overview.md @@ -1,15 +1,17 @@ # Overview -There are two aspects to logging in Pode: Methods and Types. +Logging in Pode consists of two main components: Methods and Types. -* Methods define how log items should be recorded, such as to a file, terminal, or event viewer. -* Types define how items to log are transformed, and what should be supplied to the Method. +- **Methods**: Define how log items should be recorded, such as to a file, terminal, or event viewer. Each logging method operates in its own runspace, providing isolation and efficiency. The exception to this is the Custom method, which by default runs in the same runspace as the log dispatcher unless the `-UseRunspace` parameter is specified. -For example when you supply an Exception to [`Write-PodeErrorLog`](../../../Functions/Logging/Write-PodeErrorLog), this Exception is first supplied to Pode's inbuilt Error logging type. This type transforms any Exception (or Error Record) into a string which can then be supplied to the File logging method. +- **Types**: Define how log items are transformed and what data should be supplied to the Method. -In Pode you can use File, Terminal, Event Viewer, or a Custom method. As well as Request, Error, or a Custom type. +When you supply an Exception to [`Write-PodeErrorLog`](../../../Functions/Logging/Write-PodeErrorLog), the Exception is first processed by Pode's built-in Error logging type. This type transforms the Exception (or Error Record) into a string format, which can then be recorded by the logging method (e.g., File). + +Pode supports various logging methods, including File, Terminal, Event Viewer, Syslog, Restful, or Custom methods. Additionally, you can utilize different logging types such as Request, Error, or Custom types. + +This flexibility allows you to create a custom logging method that can output logs to various platforms, such as an S3 bucket, Splunk, or any other logging service. -This means you could write a logging method to output to an S3 bucket, Splunk, or any other logging platform. ## Masking Values @@ -109,3 +111,50 @@ Instead of writing logs one-by-one, the above will keep transformed log items in This means that the method's scriptblock will receive an array of items, rather than a single item. You can also sent a `-BatchTimeout` value, in seconds, so that if your batch size it 10 but only 5 log items are added, then after the timeout value the logs items will be sent to your method. + + + +## Configuring Failure Actions for Log Writing + +Defines the behavior in case of failure to write a log. This can happen if the disk is full, the Syslog server is offline, or if the number of logs in the queue reaches the maximum allowed. The options are: +- **Ignore** : Does nothing and continues execution. **(Default)** +- **Report** : Writes a message to the console for any failure. +- **Halt** : Writes a message to the console and shuts down the Pode server. + +```powershell +New-PodeLoggingMethod -File -Path './logs' -Name 'errors' -FailureAction 'Report' | Enable-PodeRequestLogging +``` + +## QueueLimit +Defines the maximum number of logs allowed in the queue before throwing an event. +The default value is 500. The exception is handled based on the `-FailureAction` parameter. + +```powershell +@{ + Server = @{ + Logging = @{ + QueueLimit = 1000 + } + } +} +``` + +## DataFormat +The date format to use for the log entries. The default format is `'dd/MMM/yyyy:HH:mm:ss zzz'`. + +```powershell +New-PodeLoggingMethod -File -Path './logs' -Name 'access' -DataFormat 'yyyy-MM-dd HH:mm:ss' | Enable-PodeErrorLogging +``` + +## ISO8601 +If set, the date format will be ISO 8601 compliant (equivalent to `-DataFormat 'yyyy-MM-ddTHH:mm:ssK'`). This parameter is mutually exclusive with DataFormat. + +```powershell +New-PodeLoggingMethod -File -Path './logs' -Name 'access' -ISO8601 | Enable-PodeErrorLogging +``` + +## AsUTC +If set, the time will be logged in UTC instead of local time. + +```powershell +New-PodeLoggingMethod -File -Path './logs' -Name 'access' -AsUTC -ISO8601 | Enable-PodeErrorLogging \ No newline at end of file diff --git a/docs/Tutorials/Logging/Types/General.md b/docs/Tutorials/Logging/Types/General.md new file mode 100644 index 000000000..62625e5f1 --- /dev/null +++ b/docs/Tutorials/Logging/Types/General.md @@ -0,0 +1,70 @@ + +# General Logging + +Pode supports general logging, allowing you to define custom logging methods and log levels. This feature enables you to write logs based on specified methods, ensuring flexibility and control over logging outputs. + +To enable general logging, use the `Enable-PodeGeneralLogging` function. This function takes a hashtable defining the logging method, including a ScriptBlock for log output. You can specify various log levels to be enabled, such as Error, Emergency, Alert, Critical, Warning, Notice, Informational, Info, Verbose, and Debug. + +## Enabling General Logging + +To enable general logging, use the `Enable-PodeGeneralLogging` function, supplying the necessary parameters: + +- `Method`: The hashtable defining the logging method, including the ScriptBlock for log output. +- `Levels`: An array of log levels to be enabled for the logging method (default includes Error, Emergency, Alert, Critical, Warning, Notice, Informational, Info, Verbose, Debug). +- `Name`: The name of the logging method to be enabled. +- `Raw`: If set, the raw log data will be included in the logging output. + +### Example + +```powershell +$method = New-PodeLoggingMethod -syslog -Server 127.0.0.1 -Transport UDP +$method | Enable-PodeGeneralLogging -Name "mysyslog" +``` + +## Disabling General Logging + +To disable a general logging method, use the `Disable-PodeGeneralLogging` function with the `Name` parameter: + +### Example + +```powershell +Disable-PodeGeneralLogging -Name 'mysyslog' +``` + +With these functions, Pode ensures robust and customizable logging capabilities, allowing you to manage logs effectively based on your specific requirements. + +## Writing to General Logs + +Pode allows you to write logs to configured custom or inbuilt logging methods using the `Write-PodeLog` function. This function supports both custom and inbuilt logging methods, enabling structured logging with various log levels and messages. + +### Writing to General Logs + +To write logs, you can use the `Write-PodeLog` function with different parameters to specify the logging method, log level, message, and other details. + +#### Example Usage + +##### Logging an Object + +To write an object to a configured logging method: + +```powershell +$logItem = @{ + Date = [datetime]::Now + Level = 'Informational' + Server = 'MyServer' + Category = 'General' + Message = 'This is a log message' + StackTrace = '' +} +$logItem | Write-PodeLog -Name 'mysyslog' +``` + +##### Logging with Custom Levels and Messages + +To log a custom message with a specific log level: + +```powershell +Write-PodeLog -Name 'mysyslog' -Level 'Error' -Message 'An error occurred.' -Tag 'MyApp' +``` + +In these examples, `Write-PodeLog` is used to write structured log items or custom messages to the specified logging methods, helping you maintain organized and detailed logs. \ No newline at end of file diff --git a/docs/Tutorials/Logging/Types/Requests.md b/docs/Tutorials/Logging/Types/Requests.md index 3f31ffbaa..44654c138 100644 --- a/docs/Tutorials/Logging/Types/Requests.md +++ b/docs/Tutorials/Logging/Types/Requests.md @@ -6,7 +6,10 @@ Pode has inbuilt Request logging logic, that will parse and return a valid log i To enable and use the Request logging you use the [`Enable-PodeRequestLogging`](../../../../Functions/Logging/Enable-PodeRequestLogging) function, supplying a logging method from [`New-PodeLoggingMethod`](../../../../Functions/Logging/New-PodeLoggingMethod). -The Request type logic will format a string using [Combined Log Format](https://httpd.apache.org/docs/1.3/logs.html#combined). This string is then supplied to the logging method's scriptblock. If you're using a Custom logging method and want the raw hashtable instead, you can supply `-Raw` to [`Enable-PodeRequestLogging`](../../../../Functions/Logging/Enable-PodeRequestLogging). +The Request type logic will format a string using [Combined Log Format](https://httpd.apache.org/docs/1.3/logs.html#combined). +This string is then supplied to the logging method's scriptblock. You can customize the log format using the `-LogFormat` parameter with options like `Extended`, `Common`, `Combined`, and `JSON`. + +If you're using a Custom logging method and want the raw hashtable instead, you can supply `-Raw` to [`Enable-PodeRequestLogging`](../../../../Functions/Logging/Enable-PodeRequestLogging). ## Examples @@ -18,6 +21,23 @@ The following example simply enables Request logging, and will output all items New-PodeLoggingMethod -Terminal | Enable-PodeRequestLogging ``` +### Log Format + +#### Extended Log Format +The following example enables Request logging using the Extended Log Format: + +```powershell +New-PodeLoggingMethod -File -Path './logs' -Name 'requests' | Enable-PodeRequestLogging -LogFormat 'Extended' +``` + +#### JSON Format +The following example enables Request logging using JSON Format: + +```powershell +New-PodeLoggingMethod -File -Path './logs' -Name 'requests' | Enable-PodeRequestLogging -LogFormat 'Json' +``` + + ### Using Raw Item The following example uses a Custom logging method, and sets Request logging to return and supply the raw hashtable to the Custom method's scriptblock. The Custom method simply logs the Host an StatusCode to the terminal (but could be to something like an S3 bucket): @@ -70,4 +90,4 @@ The raw Request hashtable that will be supplied to any Custom logging methods wi Size = '9001' } } -``` +``` \ No newline at end of file diff --git a/docs/Tutorials/Logging/Types/Trace.md b/docs/Tutorials/Logging/Types/Trace.md new file mode 100644 index 000000000..22297053f --- /dev/null +++ b/docs/Tutorials/Logging/Types/Trace.md @@ -0,0 +1,63 @@ + +# Trace Logging + +Pode supports logging for any public function invocation and some important non-error messages using the inbuilt Trace logging method. This method enables comprehensive logging for various operations within Pode ensuring that important actions and messages are captured. + +## Enabling + +To enable and use the Trace logging you use the `Enable-PodeTraceLogging` function supplying a logging method from `New-PodeLoggingMethod`. + +## Examples + +### Log to Syslog + +The following example enables Trace logging and will output all items to a Syslog server: + +```powershell +$method = New-PodeLoggingMethod -Syslog -Server '127.0.0.1' -Transport 'UDP' +$method | Enable-PodeTraceLogging +``` + +### Using Raw Data + +The following example uses a Custom logging method and sets Trace logging to include raw log data in the logging output. The Custom method logs the raw data to the terminal: + +```powershell +$method = New-PodeLoggingMethod -Custom -ScriptBlock { + param($item) + "$($item | ConvertTo-Json -Depth 10)" | Out-Default +} + +$method | Enable-PodeTraceLogging -Raw +``` + +## Raw Trace Log Data + +The raw log data hashtable that will be supplied to any Custom logging methods will look as follows: + +```powershell +@{ + 'Server' = 'pode-dev' + 'Message' = 'Operation Add-PodeRoute invoked with parameters= ScriptBlock= Method=Get Path=/' + 'ThreadId' = 21 + 'Operation' = 'Add-PodeRoute' + 'Level' = 'Info' + 'Date' = '2024-06-18T21=31=36.4636345Z' + 'Parameters' = @{ + 'ScriptBlock' = @{ + 'Attributes' = '' + 'File' = 'C=\\Users\\pode\\Documents\\GitHub\\Pode\\examples\\logging.ps1' + 'IsFilter' = $false + 'IsConfiguration' = $false + 'Module' = $null + 'StartPosition' = 'System.Management.Automation.PSToken' + 'DebuggerHidden' = $false + 'Id' = '0de89f0a-4df2-4160-a605-97fd90c2ae2e' + 'Ast' = "{\r\n Write-PodeLog -Name 'mylog' -Message 'Something' -Level 'Info'\r\n }" + } + 'Method' = @('Get') + 'Path' = '/' + } + 'Tag' = 'Main' +} +``` \ No newline at end of file diff --git a/examples/Logging.ps1 b/examples/Logging.ps1 index 2625991bf..3e426b289 100644 --- a/examples/Logging.ps1 +++ b/examples/Logging.ps1 @@ -22,11 +22,20 @@ License: MIT License #> + +param( + [ValidateSet('Terminal', 'File', 'mylog', 'Syslog', 'EventViewer', 'Custom')] + [string[]] + $LoggingType = @( 'file', 'Custom', 'Syslog'), + + [switch] + $Raw +) + try { - # Determine the script path and Pode module path + #Determine the script path and Pode module path $ScriptPath = (Split-Path -Parent -Path $MyInvocation.MyCommand.Path) $podePath = Split-Path -Parent -Path $ScriptPath - # Import the Pode module from the source path if it exists, otherwise from installed modules if (Test-Path -Path "$($podePath)/src/Pode.psm1" -PathType Leaf) { Import-Module "$($podePath)/src/Pode.psm1" -Force -ErrorAction Stop @@ -36,44 +45,79 @@ try { } } catch { throw } - # or just: # Import-Module Pode -$LOGGING_TYPE = 'terminal' # Terminal, File, Custom - # create a server, and start listening on port 8081 -Start-PodeServer { +Start-PodeServer -browse { Add-PodeEndpoint -Address localhost -Port 8081 -Protocol Http Set-PodeViewEngine -Type Pode + $logging = @() - switch ($LOGGING_TYPE.ToLowerInvariant()) { - 'terminal' { - New-PodeLoggingMethod -Terminal | Enable-PodeRequestLogging + if ( $LoggingType -icontains 'terminal') { + $logging += New-PodeLoggingMethod -Terminal + } + + if ( $LoggingType -icontains 'file') { + $logging += New-PodeLoggingMethod -File -Name 'general' -MaxDays 4 -Format Simple -ISO8601 + $requestLogging = New-PodeLoggingMethod -File -Name 'requests' -MaxDays 4 + } + + if ( $LoggingType -icontains 'custom') { + $logging += New-PodeLoggingMethod -Custom -ArgumentList 'arg1', 'arg2', 'arg3' -ScriptBlock { + param($item, $arg1 , $arg2, $arg3, $rawItem) + $item | Out-File './examples/logs/customLegacy.log' -Append + $arg1 , $arg2, $arg3 -join ',' | Out-File './examples/logs/customLegacy_argumentList.log' -Append + $rawItem | Out-File './examples/logs/customLegacy_rawItem.log' -Append } - 'file' { - New-PodeLoggingMethod -File -Name 'requests' -MaxDays 4 | Enable-PodeRequestLogging + $logging += New-PodeLoggingMethod -Custom -UseRunspace -CustomOptions @{ 'opt1' = 'something'; 'opt2' = 'else' } -ScriptBlock { + $item | Out-File './examples/logs/customWithRunspace.log' -Append + $options | Out-File './examples/logs/customWithRunspace_options.log' -Append + $rawItem | Out-File './examples/logs/customWithRunspace_rawItem.log' -Append } + } - 'custom' { - $type = New-PodeLoggingMethod -Custom -ScriptBlock { - param($item) - # send request row to S3 - } + if ( $LoggingType -icontains 'eventviewer') { + $logging += New-PodeLoggingMethod -EventViewer + } - $type | Enable-PodeRequestLogging - } + if ( $LoggingType -icontains 'syslog') { + $logging += New-PodeLoggingMethod -syslog -Server 127.0.0.1 -Transport UDP -AsUTC -ISO8601 -FailureAction Report + } + + if ($logging.Count -eq 0) { + throw 'No logging selected' } + if ( $requestLogging) { + $requestLogging | Enable-PodeRequestLogging + } + + $logging | Enable-PodeTraceLogging -Raw:$Raw + $logging | Enable-PodeErrorLogging -Raw:$Raw -Levels * + $logging | Enable-PodeGeneralLogging -Name 'mylog' -Raw:$Raw + Write-PodeLog -Name 'mylog' -Message 'just started' -Level 'Info' # GET request for web page on "localhost:8081/" Add-PodeRoute -Method Get -Path '/' -ScriptBlock { + Write-PodeLog -Name 'mylog' -Message 'My custom log' -Level 'Info' Write-PodeViewResponse -Path 'simple' -Data @{ 'numbers' = @(1, 2, 3); } } # GET request throws fake "500" server error status code Add-PodeRoute -Method Get -Path '/error' -ScriptBlock { + Disable-PodeRequestLogging + Set-PodeResponseStatus -Code 500 + } + + Add-PodeRoute -Method Get -Path '/exception' -ScriptBlock { + try { + throw 'something happened' + } + catch { + $_ | Write-PodeErrorLog + } Set-PodeResponseStatus -Code 500 } @@ -82,4 +126,4 @@ Start-PodeServer { Set-PodeResponseAttachment -Path 'Anger.jpg' } -} +} \ No newline at end of file diff --git a/examples/server.psd1 b/examples/server.psd1 index d1858842e..a6515f506 100644 --- a/examples/server.psd1 +++ b/examples/server.psd1 @@ -40,6 +40,7 @@ '(?AppleWebKit\/)\d+\.\d+(?(= 0; i--) { Sockets[i].Dispose(); } Sockets.Clear(); - PodeHelpers.WriteErrorMessage($"Closed sockets", this, PodeLoggingLevel.Verbose); + PodeLogger.WriteErrorMessage($"Closed sockets", this, PodeLoggingLevel.Verbose); // close existing contexts - PodeHelpers.WriteErrorMessage($"Closing contexts", this, PodeLoggingLevel.Verbose); + PodeLogger.WriteErrorMessage($"Closing contexts", this, PodeLoggingLevel.Verbose); foreach (var _context in Contexts.ToArray()) { _context.Dispose(true); } Contexts.Clear(); - PodeHelpers.WriteErrorMessage($"Closed contexts", this, PodeLoggingLevel.Verbose); + PodeLogger.WriteErrorMessage($"Closed contexts", this, PodeLoggingLevel.Verbose); // close connected signals - PodeHelpers.WriteErrorMessage($"Closing signals", this, PodeLoggingLevel.Verbose); + PodeLogger.WriteErrorMessage($"Closing signals", this, PodeLoggingLevel.Verbose); foreach (var _signal in Signals.Values.ToArray()) { _signal.Dispose(); } Signals.Clear(); - PodeHelpers.WriteErrorMessage($"Closed signals", this, PodeLoggingLevel.Verbose); + PodeLogger.WriteErrorMessage($"Closed signals", this, PodeLoggingLevel.Verbose); // close connected server events - PodeHelpers.WriteErrorMessage($"Closing server events", this, PodeLoggingLevel.Verbose); + PodeLogger.WriteErrorMessage($"Closing server events", this, PodeLoggingLevel.Verbose); foreach (var _sseName in ServerEvents.Values.ToArray()) { foreach (var _sse in _sseName.Values.ToArray()) @@ -307,7 +307,7 @@ protected override void Close() } ServerEvents.Clear(); - PodeHelpers.WriteErrorMessage($"Closed server events", this, PodeLoggingLevel.Verbose); + PodeLogger.WriteErrorMessage($"Closed server events", this, PodeLoggingLevel.Verbose); } } } \ No newline at end of file diff --git a/src/Listener/PodeLogger.cs b/src/Listener/PodeLogger.cs new file mode 100644 index 000000000..9002139f7 --- /dev/null +++ b/src/Listener/PodeLogger.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Concurrent; +using System.Collections; +using System.Linq; + +namespace Pode +{ + public static class PodeLogger + { + // Static fields to store the logging status and the queue of log entries + private static bool _enabled; + private static ConcurrentQueue _queue; + + // Static property to enable or disable writing logs to the console + public static bool Terminal { get; set; } + + + // Static property to enable or disable logging + public static bool Enabled + { + get => _enabled; + set + { + _enabled = value; + if (_enabled) + { + // Initialize the queue if logging is enabled + _queue = new ConcurrentQueue(); + } + else + { + // Clear the queue if logging is disabled + _queue = null; + } + } + } + + // Property to get the count of items in the queue + public static int Count + { + get => _queue != null ? _queue.Count : 0; + } + + // Method to add a Hashtable to the queue + public static void Enqueue(Hashtable table) + { + if (_queue != null) + { + _queue.Enqueue(table); + } + } + + // Method to try and dequeue a Hashtable from the queue + public static bool TryDequeue(out Hashtable table) + { + if (_queue != null) + { + return _queue.TryDequeue(out table); + } + table = null; + return false; + } + + // Method to dequeue a Hashtable from the queue + public static Hashtable Dequeue() + { + if (_queue != null && _queue.TryDequeue(out Hashtable table)) + { + return table; + } + return null; + } + + // Method to clear the queue + public static void Clear() + { + if (_queue != null) + { + if (_queue != null) + { + while (_queue.TryDequeue(out _)) { } + } + } + } + // Method to log an exception + public static void WriteException(Exception ex, PodeConnector connector = default(PodeConnector), PodeLoggingLevel level = PodeLoggingLevel.Error) + { + if (ex == default(Exception)) + { + return; + } + + // Return if logging is disabled, or if the level isn't being logged + if (connector != default(PodeConnector) && (!connector.ErrorLoggingEnabled || !connector.ErrorLoggingLevels.Contains(level.ToString(), StringComparer.InvariantCultureIgnoreCase))) + { + return; + } + + // Write the exception to the console if Terminal is enabled + if (Terminal) + { + Console.WriteLine($"[{level}] {ex.GetType().Name}: {ex.Message}"); + Console.WriteLine(ex.StackTrace); + + if (ex.InnerException != null) + { + Console.WriteLine($"[{level}] {ex.InnerException.GetType().Name}: {ex.InnerException.Message}"); + Console.WriteLine(ex.InnerException.StackTrace); + } + } + + // Add the exception to the log queue if logging is enabled + if (Enabled) + { + Hashtable logEntry = new Hashtable + { + ["Name"] = "Listener", + ["Item"] = ex + }; + + Enqueue(logEntry); + } + } + + // Method to log an error message + public static void WriteErrorMessage(string message, PodeConnector connector = default(PodeConnector), PodeLoggingLevel level = PodeLoggingLevel.Error, PodeContext context = default(PodeContext)) + { + // Do nothing if the message is empty or whitespace + if (string.IsNullOrWhiteSpace(message)) + { + return; + } + + // Return if logging is disabled, or if the level isn't being logged + if (connector != default(PodeConnector) && (!connector.ErrorLoggingEnabled || !connector.ErrorLoggingLevels.Contains(level.ToString(), StringComparer.InvariantCultureIgnoreCase))) + { + return; + } + + // Write the message to the console if Terminal is enabled + if (Terminal) + { + if (context == default(PodeContext)) + { + Console.WriteLine($"[{level}]: {message}"); + } + else + { + Console.WriteLine($"[{level}]: [ContextId: {context.ID}] {message}"); + } + } + + // Add the error message to the log queue if logging is enabled + if (Enabled) + { + Hashtable logEntry = new Hashtable + { + ["Name"] = "Listener", + ["Item"] = new Hashtable + { + ["Message"] = message, + ["Level"] = level, + ["ThreadId"] = Environment.CurrentManagedThreadId + } + }; + + if (context != null) + { + ((Hashtable)logEntry["Item"])["TargetObject"] = context.ID; + } + + Enqueue(logEntry); + } + } + } +} diff --git a/src/Listener/PodeReceiver.cs b/src/Listener/PodeReceiver.cs index 21cff8f2c..267fa897e 100644 --- a/src/Listener/PodeReceiver.cs +++ b/src/Listener/PodeReceiver.cs @@ -93,7 +93,7 @@ public void RemoveProcessingWebSocketRequest(PodeWebSocketRequest request) protected override void Close() { // disconnect websockets - PodeHelpers.WriteErrorMessage($"Closing client web sockets", this, PodeLoggingLevel.Verbose); + PodeLogger.WriteErrorMessage($"Closing client web sockets", this, PodeLoggingLevel.Verbose); foreach (var _webSocket in WebSockets.Values.ToArray()) { @@ -101,10 +101,10 @@ protected override void Close() } WebSockets.Clear(); - PodeHelpers.WriteErrorMessage($"Closed client web sockets", this, PodeLoggingLevel.Verbose); + PodeLogger.WriteErrorMessage($"Closed client web sockets", this, PodeLoggingLevel.Verbose); // close existing websocket requests - PodeHelpers.WriteErrorMessage($"Closing client web sockets requests", this, PodeLoggingLevel.Verbose); + PodeLogger.WriteErrorMessage($"Closing client web sockets requests", this, PodeLoggingLevel.Verbose); foreach (var _req in Requests.ToArray()) { @@ -112,7 +112,7 @@ protected override void Close() } Requests.Clear(); - PodeHelpers.WriteErrorMessage($"Closed client web requests", this, PodeLoggingLevel.Verbose); + PodeLogger.WriteErrorMessage($"Closed client web requests", this, PodeLoggingLevel.Verbose); } } } \ No newline at end of file diff --git a/src/Listener/PodeRequest.cs b/src/Listener/PodeRequest.cs index bc3bfc07e..06713aeed 100644 --- a/src/Listener/PodeRequest.cs +++ b/src/Listener/PodeRequest.cs @@ -117,7 +117,7 @@ public async Task UpgradeToSSL(CancellationToken cancellationToken) catch (ObjectDisposedException) { } catch (Exception ex) { - PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Error); + PodeLogger.WriteException(ex, Context.Listener, PodeLoggingLevel.Error); Error = new HttpRequestException(ex.Message, ex); Error.Data.Add("PodeStatusCode", 502); } @@ -182,12 +182,12 @@ public async Task Receive(CancellationToken cancellationToken) catch (IOException) { } catch (HttpRequestException httpex) { - PodeHelpers.WriteException(httpex, Context.Listener, PodeLoggingLevel.Error); + PodeLogger.WriteException(httpex, Context.Listener, PodeLoggingLevel.Error); Error = httpex; } catch (Exception ex) { - PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Error); + PodeLogger.WriteException(ex, Context.Listener, PodeLoggingLevel.Error); Error = new HttpRequestException(ex.Message, ex); Error.Data.Add("PodeStatusCode", 400); } @@ -303,7 +303,7 @@ public virtual void Dispose() } PartialDispose(); - PodeHelpers.WriteErrorMessage($"Request disposed", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.WriteErrorMessage($"Request disposed", Context.Listener, PodeLoggingLevel.Verbose, Context); } } } \ No newline at end of file diff --git a/src/Listener/PodeResponse.cs b/src/Listener/PodeResponse.cs index 786545994..7ef174d65 100644 --- a/src/Listener/PodeResponse.cs +++ b/src/Listener/PodeResponse.cs @@ -92,13 +92,13 @@ public async Task Send() return; } - PodeHelpers.WriteErrorMessage($"Sending response", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.WriteErrorMessage($"Sending response", Context.Listener, PodeLoggingLevel.Verbose, Context); try { await SendHeaders(Context.IsTimeout).ConfigureAwait(false); await SendBody(Context.IsTimeout).ConfigureAwait(false); - PodeHelpers.WriteErrorMessage($"Response sent", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.WriteErrorMessage($"Response sent", Context.Listener, PodeLoggingLevel.Verbose, Context); } catch (OperationCanceledException) { } catch (IOException) { } @@ -108,7 +108,7 @@ public async Task Send() } catch (Exception ex) { - PodeHelpers.WriteException(ex, Context.Listener); + PodeLogger.WriteException(ex, Context.Listener); throw; } finally @@ -124,13 +124,13 @@ public async Task SendTimeout() return; } - PodeHelpers.WriteErrorMessage($"Sending response timed-out", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.WriteErrorMessage($"Sending response timed-out", Context.Listener, PodeLoggingLevel.Verbose, Context); StatusCode = 408; try { await SendHeaders(true).ConfigureAwait(false); - PodeHelpers.WriteErrorMessage($"Response timed-out sent", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.WriteErrorMessage($"Response timed-out sent", Context.Listener, PodeLoggingLevel.Verbose, Context); } catch (OperationCanceledException) { } catch (IOException) { } @@ -140,7 +140,7 @@ public async Task SendTimeout() } catch (Exception ex) { - PodeHelpers.WriteException(ex, Context.Listener); + PodeLogger.WriteException(ex, Context.Listener); throw; } finally @@ -366,7 +366,7 @@ public async Task Write(byte[] buffer, bool flush = false) } catch (Exception ex) { - PodeHelpers.WriteException(ex, Context.Listener); + PodeLogger.WriteException(ex, Context.Listener); throw; } } @@ -462,7 +462,7 @@ public void Dispose() OutputStream = default; } - PodeHelpers.WriteErrorMessage($"Response disposed", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.WriteErrorMessage($"Response disposed", Context.Listener, PodeLoggingLevel.Verbose, Context); } } } \ No newline at end of file diff --git a/src/Listener/PodeResponseHeaders.cs b/src/Listener/PodeResponseHeaders.cs index e619a5a86..aa2704178 100644 --- a/src/Listener/PodeResponseHeaders.cs +++ b/src/Listener/PodeResponseHeaders.cs @@ -7,7 +7,7 @@ public class PodeResponseHeaders { public object this[string name] { - get => (Headers.ContainsKey(name) ? Headers[name][0] : string.Empty); + get => Headers.TryGetValue(name, out IList value) ? value[0] : string.Empty; set => Set(name, value); } @@ -28,14 +28,15 @@ public bool ContainsKey(string name) public IList Get(string name) { - return Headers.ContainsKey(name) ? Headers[name] : default(IList); + return Headers.TryGetValue(name, out IList value) ? value : default(IList); } public void Set(string name, object value) { - if (!Headers.ContainsKey(name)) + if (!Headers.TryGetValue(name, out var list)) { - Headers.Add(name, new List()); + list = new List(); + Headers[name] = list; } Headers[name].Clear(); diff --git a/src/Listener/PodeSignalRequest.cs b/src/Listener/PodeSignalRequest.cs index f7acc49cb..63fb619a7 100644 --- a/src/Listener/PodeSignalRequest.cs +++ b/src/Listener/PodeSignalRequest.cs @@ -129,11 +129,12 @@ protected override async Task Parse(byte[] bytes, CancellationToken cancel public override void Dispose() { - // send close frame + // Check if already disposed to avoid repeated cleanup if (!IsDisposed) { - PodeHelpers.WriteErrorMessage($"Closing Websocket", Context.Listener, PodeLoggingLevel.Verbose, Context); - Context.Response.WriteFrame(string.Empty, PodeWsOpCode.Close).Wait(); + // Log and send the close frame + PodeLogger.WriteErrorMessage($"Closing Websocket", Context.Listener, PodeLoggingLevel.Verbose, Context); + _ = Context.Response.WriteFrame(string.Empty, PodeWsOpCode.Close); } // remove client, and dispose diff --git a/src/Listener/PodeSmtpRequest.cs b/src/Listener/PodeSmtpRequest.cs index 6bb1290d5..ecb125d80 100644 --- a/src/Listener/PodeSmtpRequest.cs +++ b/src/Listener/PodeSmtpRequest.cs @@ -265,7 +265,7 @@ protected override async Task Parse(byte[] bytes, CancellationToken cancel public void Reset() { - PodeHelpers.WriteErrorMessage($"Request reset", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.WriteErrorMessage($"Request reset", Context.Listener, PodeLoggingLevel.Verbose, Context); _canProcess = false; Headers = new Hashtable(StringComparer.InvariantCultureIgnoreCase); diff --git a/src/Listener/PodeSocket.cs b/src/Listener/PodeSocket.cs index 7c337c9e3..e6c784585 100644 --- a/src/Listener/PodeSocket.cs +++ b/src/Listener/PodeSocket.cs @@ -128,7 +128,7 @@ private async Task StartReceive(Socket acceptedSocket) // create the context var context = new PodeContext(acceptedSocket, this, Listener); - PodeHelpers.WriteErrorMessage($"Opening Receive", Listener, PodeLoggingLevel.Verbose, context); + PodeLogger.WriteErrorMessage($"Opening Receive", Listener, PodeLoggingLevel.Verbose, context); // initialise the context await context.Initialise().ConfigureAwait(false); @@ -144,7 +144,7 @@ private async Task StartReceive(Socket acceptedSocket) public void StartReceive(PodeContext context) { - PodeHelpers.WriteErrorMessage($"Starting Receive", Listener, PodeLoggingLevel.Verbose, context); + PodeLogger.WriteErrorMessage($"Starting Receive", Listener, PodeLoggingLevel.Verbose, context); try { @@ -159,7 +159,7 @@ public void StartReceive(PodeContext context) } catch (Exception ex) { - PodeHelpers.WriteException(ex, Listener); + PodeLogger.WriteException(ex, Listener); context.Socket.Close(); } } @@ -179,7 +179,7 @@ private void ProcessAccept(SocketAsyncEventArgs args) { if (error != SocketError.Success) { - PodeHelpers.WriteErrorMessage($"Closing accepting socket: {error}", Listener, PodeLoggingLevel.Debug); + PodeLogger.WriteErrorMessage($"Closing accepting socket: {error}", Listener, PodeLoggingLevel.Debug); } // close socket @@ -205,7 +205,7 @@ private void ProcessAccept(SocketAsyncEventArgs args) } catch (Exception ex) { - PodeHelpers.WriteException(ex, Listener); + PodeLogger.WriteException(ex, Listener); } } @@ -224,7 +224,7 @@ public async Task HandleContext(PodeContext context) // if we need to exit now, dispose and exit if (context.CloseImmediately) { - PodeHelpers.WriteException(context.Request.Error, Listener); + PodeLogger.WriteException(context.Request.Error, Listener); context.Dispose(true); process = false; } @@ -270,20 +270,20 @@ public async Task HandleContext(PodeContext context) { if (context.IsWebSocket) { - PodeHelpers.WriteErrorMessage($"Received client signal", Listener, PodeLoggingLevel.Verbose, context); + PodeLogger.WriteErrorMessage($"Received client signal", Listener, PodeLoggingLevel.Verbose, context); Listener.AddClientSignal(context.SignalRequest.NewClientSignal()); context.Dispose(); } else { - PodeHelpers.WriteErrorMessage($"Received request", Listener, PodeLoggingLevel.Verbose, context); + PodeLogger.WriteErrorMessage($"Received request", Listener, PodeLoggingLevel.Verbose, context); Listener.AddContext(context); } } } catch (Exception ex) { - PodeHelpers.WriteException(ex, Listener); + PodeLogger.WriteException(ex, Listener); } } @@ -360,7 +360,7 @@ public void Dispose() } catch (Exception ex) { - PodeHelpers.WriteException(ex, Listener); + PodeLogger.WriteException(ex, Listener); } } diff --git a/src/Listener/PodeTcpRequest.cs b/src/Listener/PodeTcpRequest.cs index c48982538..90e517dd9 100644 --- a/src/Listener/PodeTcpRequest.cs +++ b/src/Listener/PodeTcpRequest.cs @@ -66,7 +66,7 @@ protected override Task Parse(byte[] bytes, CancellationToken cancellation public void Reset() { - PodeHelpers.WriteErrorMessage($"Request reset", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.WriteErrorMessage($"Request reset", Context.Listener, PodeLoggingLevel.Verbose, Context); _body = string.Empty; RawBody = default; } diff --git a/src/Listener/PodeWatcher.cs b/src/Listener/PodeWatcher.cs index ed3134cb6..9dbfc63f4 100644 --- a/src/Listener/PodeWatcher.cs +++ b/src/Listener/PodeWatcher.cs @@ -52,7 +52,7 @@ public override void Start() protected override void Close() { // dispose watchers - PodeHelpers.WriteErrorMessage($"Closing file watchers", this, PodeLoggingLevel.Verbose); + PodeLogger.WriteErrorMessage($"Closing file watchers", this, PodeLoggingLevel.Verbose); foreach (var _watcher in FileWatchers.ToArray()) { @@ -60,10 +60,10 @@ protected override void Close() } FileWatchers.Clear(); - PodeHelpers.WriteErrorMessage($"Closed file watchers", this, PodeLoggingLevel.Verbose); + PodeLogger.WriteErrorMessage($"Closed file watchers", this, PodeLoggingLevel.Verbose); // dispose existing file events - PodeHelpers.WriteErrorMessage($"Closing file events", this, PodeLoggingLevel.Verbose); + PodeLogger.WriteErrorMessage($"Closing file events", this, PodeLoggingLevel.Verbose); foreach (var _evt in FileEvents.ToArray()) { @@ -71,7 +71,7 @@ protected override void Close() } FileEvents.Clear(); - PodeHelpers.WriteErrorMessage($"Closed file events", this, PodeLoggingLevel.Verbose); + PodeLogger.WriteErrorMessage($"Closed file events", this, PodeLoggingLevel.Verbose); } } } \ No newline at end of file diff --git a/src/Listener/PodeWebSocket.cs b/src/Listener/PodeWebSocket.cs index 87cf4ed79..22efd94a6 100644 --- a/src/Listener/PodeWebSocket.cs +++ b/src/Listener/PodeWebSocket.cs @@ -105,7 +105,7 @@ public async Task Receive() catch (IOException) { } catch (WebSocketException ex) { - PodeHelpers.WriteException(ex, Receiver, PodeLoggingLevel.Debug); + PodeLogger.WriteException(ex, Receiver, PodeLoggingLevel.Debug); Dispose(); } finally @@ -139,7 +139,7 @@ public async Task Disconnect(PodeWebSocketCloseFrom closeFrom) if (IsConnected) { - PodeHelpers.WriteErrorMessage($"Closing client web socket: {Name}", Receiver, PodeLoggingLevel.Verbose); + PodeLogger.WriteErrorMessage($"Closing client web socket: {Name}", Receiver, PodeLoggingLevel.Verbose); // only close output in client closing if (closeFrom == PodeWebSocketCloseFrom.Client) @@ -153,12 +153,12 @@ public async Task Disconnect(PodeWebSocketCloseFrom closeFrom) await WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false); } - PodeHelpers.WriteErrorMessage($"Closed client web socket: {Name}", Receiver, PodeLoggingLevel.Verbose); + PodeLogger.WriteErrorMessage($"Closed client web socket: {Name}", Receiver, PodeLoggingLevel.Verbose); } WebSocket.Dispose(); - WebSocket = default; - PodeHelpers.WriteErrorMessage($"Disconnected client web socket: {Name}", Receiver, PodeLoggingLevel.Verbose); + WebSocket = default(ClientWebSocket); + PodeLogger.WriteErrorMessage($"Disconnected client web socket: {Name}", Receiver, PodeLoggingLevel.Verbose); } public void Dispose() diff --git a/src/Locales/ar/Pode.psd1 b/src/Locales/ar/Pode.psd1 index b6cd8b697..ac0742500 100644 --- a/src/Locales/ar/Pode.psd1 +++ b/src/Locales/ar/Pode.psd1 @@ -153,7 +153,6 @@ parameterNotSuppliedInRequestExceptionMessage = "لم يتم توفير معلمة باسم '{0}' في الطلب أو لا توجد بيانات متاحة." cacheStorageNotFoundForSetExceptionMessage = "لم يتم العثور على مخزن ذاكرة التخزين المؤقت بالاسم '{0}' عند محاولة تعيين العنصر المخزن مؤقتًا '{1}'" methodPathAlreadyDefinedExceptionMessage = '[{0}] {1}: تم التعريف بالفعل.' - errorLoggingAlreadyEnabledExceptionMessage = 'تم تمكين تسجيل الأخطاء بالفعل.' valueForUsingVariableNotFoundExceptionMessage = "لم يتم العثور على قيمة لـ '`$using:{0}'." rapidPdfDoesNotSupportOpenApi31ExceptionMessage = 'أداة الوثائق RapidPdf لا تدعم OpenAPI 3.1' oauth2ClientSecretRequiredExceptionMessage = 'تتطلب OAuth2 سر العميل عند عدم استخدام PKCE.' @@ -281,10 +280,13 @@ invalidSchemeForAuthValidatorExceptionMessage = "تتطلب الخطة '{0}' المقدمة لمحقق المصادقة '{1}' ScriptBlock صالح." sseFailedToBroadcastExceptionMessage = 'فشل بث SSE بسبب مستوى البث SSE المحدد لـ {0}: {1}' adModuleWindowsOnlyExceptionMessage = 'وحدة Active Directory متاحة فقط على نظام Windows.' - requestLoggingAlreadyEnabledExceptionMessage = 'تم تمكين تسجيل الطلبات بالفعل.' invalidAccessControlMaxAgeDurationExceptionMessage = 'مدة Access-Control-Max-Age غير صالحة المقدمة: {0}. يجب أن تكون أكبر من 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'تعريف OpenAPI باسم {0} موجود بالفعل.' renamePodeOADefinitionTagExceptionMessage = "لا يمكن استخدام Rename-PodeOADefinitionTag داخل Select-PodeOADefinition 'ScriptBlock'." + fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "الدالة '{0}' لا تقبل مصفوفة كمدخل لأنبوب البيانات." + loggingAlreadyEnabledExceptionMessage = "تم تمكين تسجيل '{0}' بالفعل." + invalidEncodingExceptionMessage = 'ترميز غير صالح: {0}' + syslogProtocolExceptionMessage = 'يمكن لبروتوكول Syslog استخدام RFC3164 أو RFC5424 فقط.' definitionTagChangeNotAllowedExceptionMessage = 'لا يمكن تغيير علامة التعريف لمسار.' getRequestBodyNotAllowedExceptionMessage = 'لا يمكن أن تحتوي عمليات {0} على محتوى الطلب.' -} + } \ No newline at end of file diff --git a/src/Locales/de/Pode.psd1 b/src/Locales/de/Pode.psd1 index fb7b0c6ad..dcddde75d 100644 --- a/src/Locales/de/Pode.psd1 +++ b/src/Locales/de/Pode.psd1 @@ -153,7 +153,6 @@ parameterNotSuppliedInRequestExceptionMessage = "Ein Parameter namens '{0}' wurde in der Anfrage nicht angegeben oder es sind keine Daten verfügbar." cacheStorageNotFoundForSetExceptionMessage = "Der Cache-Speicher mit dem Namen '{0}' wurde nicht gefunden, als versucht wurde, das zwischengespeicherte Element '{1}' zu setzen." methodPathAlreadyDefinedExceptionMessage = '[{0}] {1}: Bereits definiert.' - errorLoggingAlreadyEnabledExceptionMessage = 'Die Fehlerprotokollierung wurde bereits aktiviert.' valueForUsingVariableNotFoundExceptionMessage = "Der Wert für '`$using:{0}' konnte nicht gefunden werden." rapidPdfDoesNotSupportOpenApi31ExceptionMessage = 'Das Dokumentationstool RapidPdf unterstützt OpenAPI 3.1 nicht.' oauth2ClientSecretRequiredExceptionMessage = 'OAuth2 erfordert ein Client Secret, wenn PKCE nicht verwendet wird.' @@ -281,10 +280,13 @@ invalidSchemeForAuthValidatorExceptionMessage = "Das bereitgestellte '{0}'-Schema für den Authentifizierungsvalidator '{1}' erfordert einen gültigen ScriptBlock." sseFailedToBroadcastExceptionMessage = 'SSE konnte aufgrund des definierten SSE-Broadcast-Levels für {0}: {1} nicht übertragen werden.' adModuleWindowsOnlyExceptionMessage = 'Active Directory-Modul nur unter Windows verfügbar.' - requestLoggingAlreadyEnabledExceptionMessage = 'Die Anforderungsprotokollierung wurde bereits aktiviert.' invalidAccessControlMaxAgeDurationExceptionMessage = 'Ungültige Access-Control-Max-Age-Dauer angegeben: {0}. Sollte größer als 0 sein.' openApiDefinitionAlreadyExistsExceptionMessage = 'Die OpenAPI-Definition mit dem Namen {0} existiert bereits.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag kann nicht innerhalb eines 'ScriptBlock' von Select-PodeOADefinition verwendet werden." + fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "Die Funktion '{0}' akzeptiert kein Array als Pipeline-Eingabe." + loggingAlreadyEnabledExceptionMessage = "Das Logging '{0}' wurde bereits aktiviert." + invalidEncodingExceptionMessage = 'Ungültige Codierung: {0}' + syslogProtocolExceptionMessage = 'Das Syslog-Protokoll kann nur RFC3164 oder RFC5424 verwenden.' definitionTagChangeNotAllowedExceptionMessage = 'Definitionstag für eine Route kann nicht geändert werden.' getRequestBodyNotAllowedExceptionMessage = '{0}-Operationen können keinen Anforderungstext haben.' } \ No newline at end of file diff --git a/src/Locales/en-us/Pode.psd1 b/src/Locales/en-us/Pode.psd1 index 53aba0d1c..2e79e41be 100644 --- a/src/Locales/en-us/Pode.psd1 +++ b/src/Locales/en-us/Pode.psd1 @@ -153,7 +153,6 @@ parameterNotSuppliedInRequestExceptionMessage = "A parameter called '{0}' was not supplied in the request or has no data available." cacheStorageNotFoundForSetExceptionMessage = "Cache storage with name '{0}' not found when attempting to set cached item '{1}'" methodPathAlreadyDefinedExceptionMessage = '[{0}] {1}: Already defined.' - errorLoggingAlreadyEnabledExceptionMessage = 'Error Logging has already been enabled.' valueForUsingVariableNotFoundExceptionMessage = "Value for '`$using:{0}' could not be found." rapidPdfDoesNotSupportOpenApi31ExceptionMessage = "The Document tool RapidPdf doesn't support OpenAPI 3.1" oauth2ClientSecretRequiredExceptionMessage = 'OAuth2 requires a Client Secret when not using PKCE.' @@ -281,10 +280,13 @@ invalidSchemeForAuthValidatorExceptionMessage = "The supplied '{0}' Scheme for the '{1}' authentication validator requires a valid ScriptBlock." sseFailedToBroadcastExceptionMessage = 'SSE failed to broadcast due to defined SSE broadcast level for {0}: {1}' adModuleWindowsOnlyExceptionMessage = 'Active Directory module only available on Windows OS.' - requestLoggingAlreadyEnabledExceptionMessage = 'Request Logging has already been enabled.' invalidAccessControlMaxAgeDurationExceptionMessage = 'Invalid Access-Control-Max-Age duration supplied: {0}. Should be greater than 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'OpenAPI definition named {0} already exists.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag cannot be used inside a Select-PodeOADefinition 'ScriptBlock'." + fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "The function '{0}' does not accept an array as pipeline input." + loggingAlreadyEnabledExceptionMessage = "Logging '{0}' has already been enabled." + invalidEncodingExceptionMessage = 'Invalid encoding: {0}' + syslogProtocolExceptionMessage = 'The Syslog protocol can use only RFC3164 or RFC5424.' definitionTagChangeNotAllowedExceptionMessage = 'Definition Tag for a Route cannot be changed.' getRequestBodyNotAllowedExceptionMessage = '{0} operations cannot have a Request Body.' } \ No newline at end of file diff --git a/src/Locales/en/Pode.psd1 b/src/Locales/en/Pode.psd1 index 8f97eead4..2f10792fb 100644 --- a/src/Locales/en/Pode.psd1 +++ b/src/Locales/en/Pode.psd1 @@ -154,7 +154,6 @@ parameterNotSuppliedInRequestExceptionMessage = "A parameter called '{0}' was not supplied in the request or has no data available." cacheStorageNotFoundForSetExceptionMessage = "Cache storage with name '{0}' not found when attempting to set cached item '{1}'" methodPathAlreadyDefinedExceptionMessage = '[{0}] {1}: Already defined.' - errorLoggingAlreadyEnabledExceptionMessage = 'Error Logging has already been enabled.' valueForUsingVariableNotFoundExceptionMessage = "Value for '`$using:{0}' could not be found." rapidPdfDoesNotSupportOpenApi31ExceptionMessage = "The Document tool RapidPdf doesn't support OpenAPI 3.1" oauth2ClientSecretRequiredExceptionMessage = 'OAuth2 requires a Client Secret when not using PKCE.' @@ -281,11 +280,13 @@ invalidSchemeForAuthValidatorExceptionMessage = "The supplied '{0}' Scheme for the '{1}' authentication validator requires a valid ScriptBlock." sseFailedToBroadcastExceptionMessage = 'SSE failed to broadcast due to defined SSE broadcast level for {0}: {1}' adModuleWindowsOnlyExceptionMessage = 'Active Directory module only available on Windows OS.' - requestLoggingAlreadyEnabledExceptionMessage = 'Request Logging has already been enabled.' invalidAccessControlMaxAgeDurationExceptionMessage = 'Invalid Access-Control-Max-Age duration supplied: {0}. Should be greater than 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'OpenAPI definition named {0} already exists.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag cannot be used inside a Select-PodeOADefinition 'ScriptBlock'." + fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "The function '{0}' does not accept an array as pipeline input." + loggingAlreadyEnabledExceptionMessage = "Logging '{0}' has already been enabled." + invalidEncodingExceptionMessage = 'Invalid encoding: {0}' + syslogProtocolExceptionMessage = 'The Syslog protocol can use only RFC3164 or RFC5424.' definitionTagChangeNotAllowedExceptionMessage = 'Definition Tag for a Route cannot be changed.' getRequestBodyNotAllowedExceptionMessage = '{0} operations cannot have a Request Body.' -} - +} \ No newline at end of file diff --git a/src/Locales/es/Pode.psd1 b/src/Locales/es/Pode.psd1 index 9ca60ee62..751944488 100644 --- a/src/Locales/es/Pode.psd1 +++ b/src/Locales/es/Pode.psd1 @@ -153,7 +153,6 @@ parameterNotSuppliedInRequestExceptionMessage = "No se ha proporcionado un parámetro llamado '{0}' en la solicitud o no hay datos disponibles." cacheStorageNotFoundForSetExceptionMessage = "No se encontró el almacenamiento en caché con el nombre '{0}' al intentar establecer el elemento en caché '{1}'." methodPathAlreadyDefinedExceptionMessage = '[{0}] {1}: Ya está definido.' - errorLoggingAlreadyEnabledExceptionMessage = 'El registro de errores ya está habilitado.' valueForUsingVariableNotFoundExceptionMessage = "No se pudo encontrar el valor para '`$using:{0}'." rapidPdfDoesNotSupportOpenApi31ExceptionMessage = 'La herramienta de documentación RapidPdf no admite OpenAPI 3.1' oauth2ClientSecretRequiredExceptionMessage = 'OAuth2 requiere un Client Secret cuando no se usa PKCE.' @@ -281,10 +280,13 @@ invalidSchemeForAuthValidatorExceptionMessage = "El esquema '{0}' proporcionado para el validador de autenticación '{1}' requiere un ScriptBlock válido." sseFailedToBroadcastExceptionMessage = 'SSE no pudo transmitir debido al nivel de transmisión SSE definido para {0}: {1}.' adModuleWindowsOnlyExceptionMessage = 'El módulo de Active Directory solo está disponible en Windows.' - requestLoggingAlreadyEnabledExceptionMessage = 'El registro de solicitudes ya está habilitado.' invalidAccessControlMaxAgeDurationExceptionMessage = 'Duración inválida para Access-Control-Max-Age proporcionada: {0}. Debe ser mayor que 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'La definición de OpenAPI con el nombre {0} ya existe.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag no se puede usar dentro de un 'ScriptBlock' de Select-PodeOADefinition." + fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "La función '{0}' no acepta una matriz como entrada de canalización." + loggingAlreadyEnabledExceptionMessage = "El registro '{0}' ya ha sido habilitado." + invalidEncodingExceptionMessage = 'Codificación inválida: {0}' + syslogProtocolExceptionMessage = 'El protocolo Syslog solo puede usar RFC3164 o RFC5424.' definitionTagChangeNotAllowedExceptionMessage = 'La etiqueta de definición para una Route no se puede cambiar.' getRequestBodyNotAllowedExceptionMessage = 'Las operaciones {0} no pueden tener un cuerpo de solicitud.' } \ No newline at end of file diff --git a/src/Locales/fr/Pode.psd1 b/src/Locales/fr/Pode.psd1 index 8b9d047d3..2db9ed111 100644 --- a/src/Locales/fr/Pode.psd1 +++ b/src/Locales/fr/Pode.psd1 @@ -153,7 +153,6 @@ parameterNotSuppliedInRequestExceptionMessage = "Un paramètre nommé '{0}' n'a pas été fourni dans la demande ou aucune donnée n'est disponible." cacheStorageNotFoundForSetExceptionMessage = "Le stockage de cache nommé '{0}' est introuvable lors de la tentative de définition de l'élément mis en cache '{1}'." methodPathAlreadyDefinedExceptionMessage = '[{0}] {1} : Déjà défini.' - errorLoggingAlreadyEnabledExceptionMessage = 'La journalisation des erreurs est déjà activée.' valueForUsingVariableNotFoundExceptionMessage = "Valeur pour '`$using:{0}' introuvable." rapidPdfDoesNotSupportOpenApi31ExceptionMessage = "L'outil de documentation RapidPdf ne prend pas en charge OpenAPI 3.1" oauth2ClientSecretRequiredExceptionMessage = "OAuth2 nécessite un Client Secret lorsque PKCE n'est pas utilisé." @@ -281,11 +280,13 @@ invalidSchemeForAuthValidatorExceptionMessage = "Le schéma '{0}' fourni pour le validateur d'authentification '{1}' nécessite un ScriptBlock valide." sseFailedToBroadcastExceptionMessage = 'SSE a échoué à diffuser en raison du niveau de diffusion SSE défini pour {0} : {1}.' adModuleWindowsOnlyExceptionMessage = 'Le module Active Directory est uniquement disponible sur Windows.' - requestLoggingAlreadyEnabledExceptionMessage = 'La journalisation des requêtes est déjà activée.' invalidAccessControlMaxAgeDurationExceptionMessage = 'Durée Access-Control-Max-Age invalide fournie : {0}. Doit être supérieure à 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'La définition OpenAPI nommée {0} existe déjà.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag ne peut pas être utilisé à l'intérieur d'un 'ScriptBlock' de Select-PodeOADefinition." + fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "La fonction '{0}' n'accepte pas un tableau en tant qu'entrée de pipeline." + loggingAlreadyEnabledExceptionMessage = "La journalisation '{0}' a déjà été activée." + invalidEncodingExceptionMessage = 'Encodage invalide : {0}' + syslogProtocolExceptionMessage = 'Le protocole Syslog ne peut utiliser que RFC3164 ou RFC5424.' definitionTagChangeNotAllowedExceptionMessage = 'Le tag de définition pour une Route ne peut pas être modifié.' getRequestBodyNotAllowedExceptionMessage = 'Les opérations {0} ne peuvent pas avoir de corps de requête.' -} - +} \ No newline at end of file diff --git a/src/Locales/it/Pode.psd1 b/src/Locales/it/Pode.psd1 index ff9ccfc73..c0d22d070 100644 --- a/src/Locales/it/Pode.psd1 +++ b/src/Locales/it/Pode.psd1 @@ -153,7 +153,6 @@ parameterNotSuppliedInRequestExceptionMessage = "Un parametro chiamato '{0}' non è stato fornito nella richiesta o non ci sono dati disponibili." cacheStorageNotFoundForSetExceptionMessage = "Memoria cache con nome '{0}' non trovata durante il tentativo di impostare l'elemento memorizzato nella cache '{1}'." methodPathAlreadyDefinedExceptionMessage = '[{0}] {1}: Già definito.' - errorLoggingAlreadyEnabledExceptionMessage = 'La registrazione degli errori è già abilitata.' valueForUsingVariableNotFoundExceptionMessage = "Impossibile trovare il valore per '`$using:{0}'." rapidPdfDoesNotSupportOpenApi31ExceptionMessage = 'Lo strumento di documentazione RapidPdf non supporta OpenAPI 3.1' oauth2ClientSecretRequiredExceptionMessage = 'OAuth2 richiede un Client Secret quando non si utilizza PKCE.' @@ -281,10 +280,13 @@ invalidSchemeForAuthValidatorExceptionMessage = "Lo schema '{0}' fornito per il validatore di autenticazione '{1}' richiede uno ScriptBlock valido." sseFailedToBroadcastExceptionMessage = 'SSE non è riuscito a trasmettere a causa del livello di trasmissione SSE definito per {0}: {1}.' adModuleWindowsOnlyExceptionMessage = 'Il modulo Active Directory è disponibile solo su Windows OS.' - requestLoggingAlreadyEnabledExceptionMessage = 'La registrazione delle richieste è già abilitata.' invalidAccessControlMaxAgeDurationExceptionMessage = 'Durata non valida fornita per Access-Control-Max-Age: {0}. Deve essere maggiore di 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'La definizione OpenAPI denominata {0} esiste già.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag non può essere utilizzato all'interno di un 'ScriptBlock' di Select-PodeOADefinition." + fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "La funzione '{0}' non accetta una matrice come input della pipeline." + loggingAlreadyEnabledExceptionMessage = "Il logging '{0}' è già stato abilitato." + invalidEncodingExceptionMessage = 'Codifica non valida: {0}' + syslogProtocolExceptionMessage = 'Il protocollo Syslog può utilizzare solo RFC3164 o RFC5424.' definitionTagChangeNotAllowedExceptionMessage = 'Il tag di definizione per una Route non può essere cambiato.' getRequestBodyNotAllowedExceptionMessage = 'Le operazioni {0} non possono avere un corpo della richiesta.' } \ No newline at end of file diff --git a/src/Locales/ja/Pode.psd1 b/src/Locales/ja/Pode.psd1 index ffc193a46..8d0ac72b1 100644 --- a/src/Locales/ja/Pode.psd1 +++ b/src/Locales/ja/Pode.psd1 @@ -153,7 +153,6 @@ parameterNotSuppliedInRequestExceptionMessage = "リクエストに '{0}' という名前のパラメータが提供されていないか、データがありません。" cacheStorageNotFoundForSetExceptionMessage = "キャッシュされたアイテム '{1}' を設定しようとしたときに、名前 '{0}' のキャッシュストレージが見つかりません。" methodPathAlreadyDefinedExceptionMessage = '[{0}] {1}: 既に定義されています。' - errorLoggingAlreadyEnabledExceptionMessage = 'エラーロギングは既に有効になっています。' valueForUsingVariableNotFoundExceptionMessage = "'`$using:{0}'の値が見つかりませんでした。" rapidPdfDoesNotSupportOpenApi31ExceptionMessage = 'ドキュメントツール RapidPdf は OpenAPI 3.1 をサポートしていません' oauth2ClientSecretRequiredExceptionMessage = 'PKCEを使用しない場合、OAuth2にはクライアントシークレットが必要です。' @@ -281,11 +280,13 @@ invalidSchemeForAuthValidatorExceptionMessage = "'{1}'認証バリデーターのために提供された'{0}'スキームには有効なScriptBlockが必要です。" sseFailedToBroadcastExceptionMessage = '{0}のSSEブロードキャストレベルが定義されているため、SSEのブロードキャストに失敗しました: {1}' adModuleWindowsOnlyExceptionMessage = 'Active DirectoryモジュールはWindowsでのみ利用可能です。' - requestLoggingAlreadyEnabledExceptionMessage = 'リクエストロギングは既に有効になっています。' invalidAccessControlMaxAgeDurationExceptionMessage = '無効な Access-Control-Max-Age 期間が提供されました:{0}。0 より大きくする必要があります。' openApiDefinitionAlreadyExistsExceptionMessage = '名前が {0} の OpenAPI 定義は既に存在します。' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag は Select-PodeOADefinition 'ScriptBlock' 内で使用できません。" + fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "関数 '{0}' は配列をパイプライン入力として受け付けません。" + loggingAlreadyEnabledExceptionMessage = "ログ記録 '{0}' は既に有効になっています。" + invalidEncodingExceptionMessage = '無効なエンコーディング: {0}' + syslogProtocolExceptionMessage = 'SyslogプロトコルはRFC3164またはRFC5424のみを使用できます。' definitionTagChangeNotAllowedExceptionMessage = 'Routeの定義タグは変更できません。' getRequestBodyNotAllowedExceptionMessage = '{0}操作にはリクエストボディを含めることはできません。' -} - +} \ No newline at end of file diff --git a/src/Locales/ko/Pode.psd1 b/src/Locales/ko/Pode.psd1 index 25e3c3d10..50b94a254 100644 --- a/src/Locales/ko/Pode.psd1 +++ b/src/Locales/ko/Pode.psd1 @@ -153,7 +153,6 @@ parameterNotSuppliedInRequestExceptionMessage = "요청에 '{0}'라는 이름의 매개변수가 제공되지 않았거나 데이터가 없습니다." cacheStorageNotFoundForSetExceptionMessage = "캐시된 항목 '{1}'을(를) 설정하려고 할 때 이름이 '{0}'인 캐시 스토리지를 찾을 수 없습니다." methodPathAlreadyDefinedExceptionMessage = '[{0}] {1}: 이미 정의되었습니다.' - errorLoggingAlreadyEnabledExceptionMessage = '오류 로깅이 이미 활성화되었습니다.' valueForUsingVariableNotFoundExceptionMessage = "'`$using:{0}'에 대한 값을 찾을 수 없습니다." rapidPdfDoesNotSupportOpenApi31ExceptionMessage = '문서 도구 RapidPdf는 OpenAPI 3.1을 지원하지 않습니다.' oauth2ClientSecretRequiredExceptionMessage = 'PKCE를 사용하지 않을 때 OAuth2에는 클라이언트 비밀이 필요합니다.' @@ -281,10 +280,13 @@ invalidSchemeForAuthValidatorExceptionMessage = "'{1}' 인증 검증기에 제공된 '{0}' 스킴에는 유효한 ScriptBlock이 필요합니다." sseFailedToBroadcastExceptionMessage = '{0}에 대해 정의된 SSE 브로드캐스트 수준으로 인해 SSE 브로드캐스트에 실패했습니다: {1}' adModuleWindowsOnlyExceptionMessage = 'Active Directory 모듈은 Windows에서만 사용할 수 있습니다.' - requestLoggingAlreadyEnabledExceptionMessage = '요청 로깅이 이미 활성화되었습니다.' invalidAccessControlMaxAgeDurationExceptionMessage = '잘못된 Access-Control-Max-Age 기간이 제공되었습니다: {0}. 0보다 커야 합니다.' openApiDefinitionAlreadyExistsExceptionMessage = '이름이 {0}인 OpenAPI 정의가 이미 존재합니다.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag은 Select-PodeOADefinition 'ScriptBlock' 내에서 사용할 수 없습니다." + fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "함수 '{0}'은(는) 배열을 파이프라인 입력으로 받지 않습니다." + loggingAlreadyEnabledExceptionMessage = "로그 '{0}'이(가) 이미 활성화되었습니다." + invalidEncodingExceptionMessage = '잘못된 인코딩: {0}' + syslogProtocolExceptionMessage = 'Syslog 프로토콜은 RFC3164 또는 RFC5424만 사용할 수 있습니다.' definitionTagChangeNotAllowedExceptionMessage = 'Route에 대한 정의 태그는 변경할 수 없습니다.' getRequestBodyNotAllowedExceptionMessage = '{0} 작업에는 요청 본문이 있을 수 없습니다.' -} + } \ No newline at end of file diff --git a/src/Locales/nl/Pode.psd1 b/src/Locales/nl/Pode.psd1 index 3f20960a0..6ae0d1817 100644 --- a/src/Locales/nl/Pode.psd1 +++ b/src/Locales/nl/Pode.psd1 @@ -154,7 +154,6 @@ parameterNotSuppliedInRequestExceptionMessage = "Een parameter genaamd '{0}' is niet opgegeven in het verzoek of heeft geen beschikbare gegevens." cacheStorageNotFoundForSetExceptionMessage = "Cache-opslag met naam '{0}' niet gevonden bij poging om gecachte item '{1}' in te stellen" methodPathAlreadyDefinedExceptionMessage = '[{0}] {1}: Al gedefinieerd.' - errorLoggingAlreadyEnabledExceptionMessage = 'Foutlogboekregistratie is al ingeschakeld.' valueForUsingVariableNotFoundExceptionMessage = "Waarde voor '`$using:{0}' kon niet worden gevonden." rapidPdfDoesNotSupportOpenApi31ExceptionMessage = 'Het Document-tool RapidPdf ondersteunt OpenAPI 3.1 niet' oauth2ClientSecretRequiredExceptionMessage = 'OAuth2 vereist een Client Secret wanneer PKCE niet wordt gebruikt.' @@ -281,11 +280,13 @@ invalidSchemeForAuthValidatorExceptionMessage = "Het opgegeven '{0}' schema voor de '{1}' authenticatievalidator vereist een geldige ScriptBlock." sseFailedToBroadcastExceptionMessage = 'SSE kon niet uitzenden vanwege het gedefinieerde SSE-uitzendniveau voor {0}: {1}' adModuleWindowsOnlyExceptionMessage = 'Active Directory-module alleen beschikbaar op Windows OS.' - requestLoggingAlreadyEnabledExceptionMessage = 'Verzoeklogboekregistratie is al ingeschakeld.' invalidAccessControlMaxAgeDurationExceptionMessage = 'Ongeldige Access-Control-Max-Age duur opgegeven: {0}. Moet groter zijn dan 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'OpenAPI-definitie met de naam {0} bestaat al.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag kan niet worden gebruikt binnen een Select-PodeOADefinition 'ScriptBlock'." + fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "De functie '{0}' accepteert geen array als pipeline-invoer." + loggingAlreadyEnabledExceptionMessage = "Logging '{0}' is al ingeschakeld." + invalidEncodingExceptionMessage = 'Ongeldige codering: {0}' + syslogProtocolExceptionMessage = 'Het Syslog-protocol kan alleen RFC3164 of RFC5424 gebruiken.' definitionTagChangeNotAllowedExceptionMessage = 'Definitietag voor een route kan niet worden gewijzigd.' getRequestBodyNotAllowedExceptionMessage = '{0}-operaties kunnen geen Request Body hebben.' -} - +} \ No newline at end of file diff --git a/src/Locales/pl/Pode.psd1 b/src/Locales/pl/Pode.psd1 index 6c4f2da03..0ed50c9c4 100644 --- a/src/Locales/pl/Pode.psd1 +++ b/src/Locales/pl/Pode.psd1 @@ -153,7 +153,6 @@ parameterNotSuppliedInRequestExceptionMessage = "Parametr o nazwie '{0}' nie został dostarczony w żądaniu lub nie ma dostępnych danych." cacheStorageNotFoundForSetExceptionMessage = "Nie znaleziono magazynu pamięci podręcznej o nazwie '{0}' podczas próby ustawienia elementu w pamięci podręcznej '{1}'." methodPathAlreadyDefinedExceptionMessage = '[{0}] {1}: Już zdefiniowane.' - errorLoggingAlreadyEnabledExceptionMessage = 'Rejestrowanie błędów jest już włączone.' valueForUsingVariableNotFoundExceptionMessage = "Nie można znaleźć wartości dla '`$using:{0}'." rapidPdfDoesNotSupportOpenApi31ExceptionMessage = 'Narzędzie do dokumentów RapidPdf nie obsługuje OpenAPI 3.1' oauth2ClientSecretRequiredExceptionMessage = 'OAuth2 wymaga tajemnicy klienta, gdy nie używa się PKCE.' @@ -281,11 +280,13 @@ invalidSchemeForAuthValidatorExceptionMessage = "Dostarczony schemat '{0}' dla walidatora uwierzytelniania '{1}' wymaga ważnego ScriptBlock." sseFailedToBroadcastExceptionMessage = 'SSE nie udało się przesłać z powodu zdefiniowanego poziomu przesyłania SSE dla {0}: {1}' adModuleWindowsOnlyExceptionMessage = 'Moduł Active Directory jest dostępny tylko w systemie Windows.' - requestLoggingAlreadyEnabledExceptionMessage = 'Rejestrowanie żądań jest już włączone.' invalidAccessControlMaxAgeDurationExceptionMessage = 'Podano nieprawidłowy czas trwania Access-Control-Max-Age: {0}. Powinien być większy niż 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'Definicja OpenAPI o nazwie {0} już istnieje.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag nie może być używany wewnątrz 'ScriptBlock' Select-PodeOADefinition." + fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "Funkcja '{0}' nie akceptuje tablicy jako wejścia potoku." + loggingAlreadyEnabledExceptionMessage = "Rejestrowanie '{0}' jest już włączone." + invalidEncodingExceptionMessage = 'Nieprawidłowe kodowanie: {0}' + syslogProtocolExceptionMessage = 'Protokół Syslog może używać tylko RFC3164 lub RFC5424.' definitionTagChangeNotAllowedExceptionMessage = 'Tag definicji dla Route nie może zostać zmieniony.' getRequestBodyNotAllowedExceptionMessage = 'Operacje {0} nie mogą mieć treści żądania.' -} - +} \ No newline at end of file diff --git a/src/Locales/pt/Pode.psd1 b/src/Locales/pt/Pode.psd1 index df0073012..39cc53e7f 100644 --- a/src/Locales/pt/Pode.psd1 +++ b/src/Locales/pt/Pode.psd1 @@ -153,7 +153,6 @@ parameterNotSuppliedInRequestExceptionMessage = "Um parâmetro chamado '{0}' não foi fornecido na solicitação ou não há dados disponíveis." cacheStorageNotFoundForSetExceptionMessage = "Armazenamento em cache com o nome '{0}' não encontrado ao tentar definir o item em cache '{1}'." methodPathAlreadyDefinedExceptionMessage = '[{0}] {1}: Já definido.' - errorLoggingAlreadyEnabledExceptionMessage = 'O registro de erros já está habilitado.' valueForUsingVariableNotFoundExceptionMessage = "Valor para '`$using:{0}' não pôde ser encontrado." rapidPdfDoesNotSupportOpenApi31ExceptionMessage = 'A ferramenta de documentos RapidPdf não suporta OpenAPI 3.1' oauth2ClientSecretRequiredExceptionMessage = 'OAuth2 requer um Client Secret quando não se usa PKCE.' @@ -281,10 +280,13 @@ invalidSchemeForAuthValidatorExceptionMessage = "O esquema '{0}' fornecido para o validador de autenticação '{1}' requer um ScriptBlock válido." sseFailedToBroadcastExceptionMessage = 'SSE falhou em transmitir devido ao nível de transmissão SSE definido para {0}: {1}.' adModuleWindowsOnlyExceptionMessage = 'O módulo Active Directory está disponível apenas no Windows.' - requestLoggingAlreadyEnabledExceptionMessage = 'O registro de solicitações já está habilitado.' invalidAccessControlMaxAgeDurationExceptionMessage = 'Duração inválida fornecida para Access-Control-Max-Age: {0}. Deve ser maior que 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'A definição OpenAPI com o nome {0} já existe.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag não pode ser usado dentro de um 'ScriptBlock' Select-PodeOADefinition." + fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "A função '{0}' não aceita uma matriz como entrada de pipeline." + loggingAlreadyEnabledExceptionMessage = "O registro '{0}' já foi habilitado." + invalidEncodingExceptionMessage = 'Codificação inválida: {0}' + syslogProtocolExceptionMessage = 'O protocolo Syslog só pode usar RFC3164 ou RFC5424.' definitionTagChangeNotAllowedExceptionMessage = 'A Tag de definição para uma Route não pode ser alterada.' getRequestBodyNotAllowedExceptionMessage = 'As operações {0} não podem ter um corpo de solicitação.' } \ No newline at end of file diff --git a/src/Locales/zh/Pode.psd1 b/src/Locales/zh/Pode.psd1 index daa9ad110..92c1f3c3a 100644 --- a/src/Locales/zh/Pode.psd1 +++ b/src/Locales/zh/Pode.psd1 @@ -153,7 +153,6 @@ parameterNotSuppliedInRequestExceptionMessage = "请求中未提供名为 '{0}' 的参数或没有可用数据。" cacheStorageNotFoundForSetExceptionMessage = "尝试设置缓存项 '{1}' 时,找不到名为 '{0}' 的缓存存储。" methodPathAlreadyDefinedExceptionMessage = '[{0}] {1}: 已经定义。' - errorLoggingAlreadyEnabledExceptionMessage = '错误日志记录已启用。' valueForUsingVariableNotFoundExceptionMessage = "未找到 '`$using:{0}' 的值。" rapidPdfDoesNotSupportOpenApi31ExceptionMessage = '文档工具 RapidPdf 不支持 OpenAPI 3.1' oauth2ClientSecretRequiredExceptionMessage = '不使用 PKCE 时, OAuth2 需要一个客户端密钥。' @@ -281,10 +280,13 @@ invalidSchemeForAuthValidatorExceptionMessage = "提供的 '{0}' 方案用于 '{1}' 身份验证验证器,需要一个有效的 ScriptBlock。" sseFailedToBroadcastExceptionMessage = '由于为{0}定义的SSE广播级别, SSE广播失败: {1}' adModuleWindowsOnlyExceptionMessage = '仅支持 Windows 的 Active Directory 模块。' - requestLoggingAlreadyEnabledExceptionMessage = '请求日志记录已启用。' invalidAccessControlMaxAgeDurationExceptionMessage = '提供的 Access-Control-Max-Age 时长无效:{0}。应大于 0。' openApiDefinitionAlreadyExistsExceptionMessage = '名为 {0} 的 OpenAPI 定义已存在。' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag 不能在 Select-PodeOADefinition 'ScriptBlock' 内使用。" + fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "函数 '{0}' 不接受数组作为管道输入。" + loggingAlreadyEnabledExceptionMessage = "日志记录 '{0}' 已启用。" + invalidEncodingExceptionMessage = '无效的编码: {0}' + syslogProtocolExceptionMessage = 'Syslog 协议只能使用 RFC3164 或 RFC5424。' definitionTagChangeNotAllowedExceptionMessage = 'Route的定义标签无法更改。' getRequestBodyNotAllowedExceptionMessage = '{0} 操作不能包含请求体。' } \ No newline at end of file diff --git a/src/Pode.psd1 b/src/Pode.psd1 index eae7d8c44..77297a3c8 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -280,15 +280,22 @@ 'New-PodeLoggingMethod', 'Enable-PodeRequestLogging', 'Enable-PodeErrorLogging', + 'Enable-PodeGeneralLogging', + 'Enable-PodeTraceLogging', 'Disable-PodeRequestLogging', 'Disable-PodeErrorLogging', + 'Disable-PodeGeneralLogging', + 'Disable-PodeTraceLogging', 'Add-PodeLogger', 'Remove-PodeLogger', - 'Clear-PodeLoggers', + 'Clear-PodeLogger', 'Write-PodeErrorLog', 'Write-PodeLog', 'Protect-PodeLogItem', 'Use-PodeLogging', + 'Enable-PodeLogging', + 'Disable-PodeLogging', + 'Clear-PodeLogging', # core 'Start-PodeServer', @@ -501,7 +508,8 @@ 'Enable-PodeOpenApiViewer', 'Enable-PodeOA', 'Get-PodeOpenApiDefinition', - 'New-PodeOASchemaProperty' + 'New-PodeOASchemaProperty', + 'Clear-PodeLoggers' ) # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. diff --git a/src/Private/Context.ps1 b/src/Private/Context.ps1 index 3e3395551..ac25dc21c 100644 --- a/src/Private/Context.ps1 +++ b/src/Private/Context.ps1 @@ -1,5 +1,55 @@ using namespace Pode +<# +.SYNOPSIS + Initializes a new Pode context with various server configurations. + +.DESCRIPTION + This function creates and initializes a new Pode context object with server configurations, including threading, schedules, tasks, logging, and more. + It ensures that essential configurations are set, and it can run in different environments such as serverless or IIS. + +.PARAMETER ScriptBlock + The script block to be executed within the Pode context. + +.PARAMETER FilePath + The file path to the script block. + +.PARAMETER Threads + The number of threads to be used. Default is 1. + +.PARAMETER Interval + The interval for server operations. Default is 0. + +.PARAMETER ServerRoot + The root path for the server. + +.PARAMETER Name + The name of the server. If not provided, a random name will be generated. + +.PARAMETER ServerlessType + Specifies if the server is running in a serverless context. + +.PARAMETER StatusPageExceptions + Configuration for displaying exceptions on the status page. + +.PARAMETER ListenerType + The type of listener to be used by the server. + +.PARAMETER EnablePool + An array of pools to enable, such as 'timers', 'tasks', 'schedules', and 'websockets'. + +.PARAMETER DisableTermination + A switch to disable server termination. + +.PARAMETER Quiet + A switch to enable quiet mode, suppressing certain outputs. + +.PARAMETER EnableBreakpoints + A switch to enable debugging breakpoints. + +.EXAMPLE + $context = New-PodeContext -ScriptBlock $script -FilePath 'path/to/file' -Threads 4 -ServerRoot 'path/to/root' +#> function New-PodeContext { [CmdletBinding()] param( @@ -76,7 +126,6 @@ function New-PodeContext { Add-Member -MemberType NoteProperty -Name Runspaces -Value $null -PassThru | Add-Member -MemberType NoteProperty -Name RunspaceState -Value $null -PassThru | Add-Member -MemberType NoteProperty -Name Tokens -Value @{} -PassThru | - Add-Member -MemberType NoteProperty -Name LogsToProcess -Value $null -PassThru | Add-Member -MemberType NoteProperty -Name Threading -Value @{} -PassThru | Add-Member -MemberType NoteProperty -Name Server -Value @{} -PassThru | Add-Member -MemberType NoteProperty -Name Metrics -Value @{} -PassThru | @@ -95,6 +144,9 @@ function New-PodeContext { $ctx.Server.Quiet = $Quiet.IsPresent $ctx.Server.ComputerName = [System.Net.DNS]::GetHostName() + # set to True after the server is started + $ctx.Server.Started = $false + # list of created listeners/receivers $ctx.Listeners = @() $ctx.Receivers = @() @@ -131,8 +183,11 @@ function New-PodeContext { # basic logging setup $ctx.Server.Logging = @{ - Enabled = $true - Types = @{} + Enabled = $true + Type = @{} + Masking = @{} + QueueLimit = 500 + Method = @{} } # set thread counts @@ -406,9 +461,6 @@ function New-PodeContext { Restart = New-Object System.Threading.CancellationTokenSource } - # requests that should be logged - $ctx.LogsToProcess = New-Object System.Collections.ArrayList - # middleware that needs to run $ctx.Server.Middleware = @() $ctx.Server.BodyParsers = @{} @@ -432,6 +484,7 @@ function New-PodeContext { Gui = $null Tasks = $null Files = $null + Logs = $null } # threading locks, etc. @@ -525,7 +578,6 @@ function New-PodeRunspacePool { $threadsCounts = @{ Default = 3 Timer = 1 - Log = 1 Schedule = 1 Misc = 1 } @@ -549,6 +601,12 @@ function New-PodeRunspacePool { State = 'Waiting' } + # logs runspace - any log is running here + $PodeContext.RunspacePools.Logs = @{ + Pool = [runspacefactory]::CreateRunspacePool(1, 1, $PodeContext.RunspaceState, $Host) + State = 'Waiting' + } + # web runspace - if we have any http/s endpoints if (Test-PodeEndpointByProtocolType -Type Http) { $PodeContext.RunspacePools.Web = @{ @@ -787,7 +845,6 @@ function New-PodeStateContext { Add-Member -MemberType NoteProperty -Name RunspacePools -Value $Context.RunspacePools -PassThru | Add-Member -MemberType NoteProperty -Name Tokens -Value $Context.Tokens -PassThru | Add-Member -MemberType NoteProperty -Name Metrics -Value $Context.Metrics -PassThru | - Add-Member -MemberType NoteProperty -Name LogsToProcess -Value $Context.LogsToProcess -PassThru | Add-Member -MemberType NoteProperty -Name Threading -Value $Context.Threading -PassThru | Add-Member -MemberType NoteProperty -Name Server -Value $Context.Server -PassThru) } @@ -844,14 +901,18 @@ function Set-PodeServerConfiguration { Files = @() } - # logging - $Context.Server.Logging = @{ - Enabled = (($null -eq $Configuration.Logging.Enable) -or [bool]$Configuration.Logging.Enable) - Masking = @{ - Patterns = (Remove-PodeEmptyItemsFromArray -Array @($Configuration.Logging.Masking.Patterns)) - Mask = (Protect-PodeValue -Value $Configuration.Logging.Masking.Mask -Default '********') + if ($Configuration.ContainsKey('Logging')) { + # logging + if ($Configuration.Logging.ContainsKey('Enable')) { + $Context.Server.Logging.Enabled = ([bool]$Configuration.Logging.Enable) + } + if ($Configuration.Logging.ContainsKey('Masking')) { + $Context.Server.Logging.Masking = @{ + Patterns = (Remove-PodeEmptyItemsFromArray -Array @($Configuration.Logging.Masking.Patterns)) + Mask = (Protect-PodeValue -Value $Configuration.Logging.Masking.Mask -Default '********') + } } - Types = @{} + $Context.Server.Logging.QueueLimit = (Protect-PodeValue -Value $Configuration.Logging.QueueLimit $Context.Server.Logging.QueueLimit) } # sockets diff --git a/src/Private/Helpers.ps1 b/src/Private/Helpers.ps1 index d9d09ded0..22978237b 100644 --- a/src/Private/Helpers.ps1 +++ b/src/Private/Helpers.ps1 @@ -554,7 +554,6 @@ function Get-PodeSubnetRange { function Add-PodeRunspace { param( [Parameter(Mandatory = $true)] - [ValidateSet('Main', 'Signals', 'Schedules', 'Gui', 'Web', 'Smtp', 'Tcp', 'Tasks', 'WebSockets', 'Files')] [string] $Type, @@ -844,6 +843,8 @@ function Close-PodeServerInternal { [switch] $ShowDoneMessage ) + #Disable Logging before closing + Disable-PodeLogging # ensure the token is cancelled if ($null -ne $PodeContext.Tokens.Cancellation) { @@ -883,6 +884,56 @@ function Close-PodeServerInternal { } } +<# +.SYNOPSIS + Waits for the Pode server to start within a specified timeout period. + +.DESCRIPTION + This function waits for the Pode server to start by checking the server status at regular intervals. + If the server does not start within the specified timeout period, the function returns $false. Otherwise, it returns $true. + If the server is already started, it immediately returns $true. + +.PARAMETER CheckInterval + The interval in milliseconds between checks to see if the server has started. Default is 1000 milliseconds (1 second). + +.PARAMETER Timeout + The maximum amount of time in milliseconds to wait for the server to start. Default is 120000 milliseconds (120 seconds). + +.EXAMPLE + $result = Wait-PodeServerToStart -CheckInterval 1000 -Timeout 30000 + if (-not $result) { + Write-Warning "The server did not start within the specified timeout period." + } +#> +function Wait-PodeServerToStart { + param ( + [int] + $CheckInterval = 1000, + [int] + $Timeout = 120000 + ) + + # Return immediately if the server is already started + if ($PodeContext.Server.Started) { + return $true + } + # Create a stopwatch to track the elapsed time + $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() + + # Wait for the server to start before processing logs or timeout + while ( -not $PodeContext.Server.Started ) { + if ($stopwatch.ElapsedMilliseconds -ge $Timeout) { + return $false # Return false if timeout is reached + } + Start-Sleep -Milliseconds $CheckInterval + } + + # Stop the stopwatch + $stopwatch.Stop() + + return $true # Return true if the server started +} + function New-PodePSDrive { param( [Parameter(Mandatory = $true)] @@ -1689,7 +1740,7 @@ function ConvertTo-PodeResponseContent { } } - { $_ -match '^(.*\/)?(.*\+)?yaml$' } { + { $_ -match '^(.*\/)?(.*\+)?yaml$' } { if ($InputObject -isnot [string]) { if ($Depth -le 0) { return (ConvertTo-PodeYamlInternal -InputObject $InputObject ) @@ -4035,4 +4086,64 @@ function Resolve-PodeObjectArray { # For any other type, convert it to a PowerShell object return New-Object psobject -Property $Property } -} \ No newline at end of file +} + +<# +.SYNOPSIS + Handles failure actions based on the provided parameters. + +.DESCRIPTION + This function processes failure scenarios by either ignoring the failure, + reporting it on the console and continuing, or reporting it on the console + and halting the server. The behavior is controlled by the 'FailureAction' + parameter. + +.PARAMETER Message + The message to be displayed in case of a failure. + +.PARAMETER FailureAction + Specifies the action to take in case of failure. Accepted values are: + - 'Ignore': Do nothing and continue execution. + - 'Report': Display the message on the console and continue execution. + - 'Halt': Display the message on the console and halt the server. + +.EXAMPLE + Invoke-PodeHandleFailure -Message "An error occurred." -FailureAction "Report" + This will display the message "An error occurred." on the console and continue execution. + +.EXAMPLE + Invoke-PodeHandleFailure -Message "Critical failure." -FailureAction "Halt" + This will display the message "Critical failure." on the console and halt the server. + +.NOTES + This is an internal function and may change in future releases of Pode. +#> +function Invoke-PodeHandleFailure { + param( + [Parameter(Mandatory = $true)] + [string] + $Message, + + [Parameter(Mandatory = $true)] + [string] + [ValidateSet('Ignore', 'Report', 'Halt' )] + $FailureAction + + ) + switch ($FailureAction.ToLowerInvariant()) { + 'ignore' { + # Do nothing and continue + } + 'report' { + # Report on console and continue + Write-PodeHost $Message -ForegroundColor Yellow + } + 'halt' { + # Report on console and halt + Write-PodeHost $Message -ForegroundColor Red + Write-PodeHost 'Pode Server shutting down.' -ForegroundColor Red + Close-PodeServer + } + } +} + diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index 21ad5b56c..08e0a8647 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -1,115 +1,628 @@ +using namespace Pode +<# +.SYNOPSIS +Defines the method for writing log messages to the terminal. + +.DESCRIPTION +This internal function handles writing log messages to the terminal. +It checks if the server is in quiet mode and protects sensitive information before outputting the log messages. + +.PARAMETER item +The log item to be written to the terminal. + +.PARAMETER options +A hashtable containing options for the terminal logging method. + +.NOTES +This is an internal function and may change in future releases of Pode. +#> function Get-PodeLoggingTerminalMethod { return { - param($item, $options) + param($MethodId) + + ([System.Management.Automation.Runspaces.Runspace]::DefaultRunspace).Name = "LoggingTerminalMethod_$MethodId" if ($PodeContext.Server.Quiet) { return } - # check if it's an array from batching - if ($item -is [array]) { - $item = ($item -join [System.Environment]::NewLine) - } + $log = @{} + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + Start-Sleep -Milliseconds 100 - # protect then write - $item = ($item | Protect-PodeLogItem) - $item.ToString() | Out-PodeHost + if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { + if ($null -ne $log) { + $Item = $log.Item + # check if it's an array from batching + if ($Item -is [array]) { + $Item = ($Item -join [System.Environment]::NewLine) + } + + # protect then write + $Item = ($Item | Protect-PodeLogItem) + $Item.ToString() | Out-PodeHost + } + } + } } } +<# +.SYNOPSIS + Defines the method for writing log messages to a file. + +.DESCRIPTION + This internal function handles writing log messages to a file, managing file rotation based on size and date, and removing old log files beyond a specified retention period. + It includes error handling based on user-defined actions. + +.PARAMETER item + The log item to be written to the file. + +.PARAMETER options + A hashtable containing options for the file logging method including Path, Name, MaxDays, MaxSize, Date, FileId, and FailureAction. +.NOTES + This is an internal function and may change in future releases of Pode. +#> function Get-PodeLoggingFileMethod { return { - param($item, $options) + param($MethodId) - # check if it's an array from batching - if ($item -is [array]) { - $item = ($item -join [System.Environment]::NewLine) + # Set the name of the default runspace + ([System.Management.Automation.Runspaces.Runspace]::DefaultRunspace).Name = "LoggingFileMethod_$MethodId" + $log = @{} + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + Start-Sleep -Milliseconds 100 + + if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { + if ($null -ne $log) { + + try { + $Item = $log.Item + $Options = $log.Options + $RawItem = $log.RawItem + + # Variables + $date = [DateTime]::Now.ToString('yyyy-MM-dd') + + # Reset the fileId if the date has changed + if ($Options.Date -ine $date) { + $Options.Date = $date + $Options.FileId = 0 + } + + # Get the fileId if it hasn't been set + if ($Options.FileId -eq 0) { + $path = [System.IO.Path]::Combine($Options.Path, "$($Options.Name)_$($date)_*.log") + $Options.FileId = (@(Get-ChildItem -Path $path)).Length + if ($Options.FileId -eq 0) { + $Options.FileId = 1 + } + } + + $id = "$($Options.FileId)".PadLeft(3, '0') + + # Check if file size exceeds MaxSize and increment fileId if necessary + if ($Options.MaxSize -gt 0) { + $path = [System.IO.Path]::Combine($Options.Path, "$($Options.Name)_$($date)_$($id).log") + if ((Get-Item -Path $path -Force).Length -ge $Options.MaxSize) { + $Options.FileId++ + $id = "$($Options.FileId)".PadLeft(3, '0') + } + } + + # Get the file to write to + $path = [System.IO.Path]::Combine($Options.Path, "$($Options.Name)_$($date)_$($id).log") + + if ($Options.Format -eq 'Default') { + # Check if the item is an array from batching + if ($Item -is [array]) { + $Item = ($Item -join [System.Environment]::NewLine) + } + + # Mask values + $outString = ($Item | Protect-PodeLogItem).ToString() + } + else { + if ($RawItem -is [array]) { + $tmpStrings = @() + foreach ($item in $RawItem) { + if ($Options.Format -eq 'Simple') { + $tmpStrings += (ConvertTo-PodeSyslogFormat -RawItem $item -MaxLength $Options.MaxLength -Source $Options.Source -DataFormat $Options.DataFormat -Separator $Options.Separator) + } + else { + $outString = ConvertTo-PodeSyslogFormat -RawItem $item -RFC $Options.Format -Source $Options.Source + } + + } + $outString = $tmpStrings -join [System.Environment]::NewLine + + } + else { + if ($Options.Format -eq 'Simple') { + $outString = ConvertTo-PodeSyslogFormat -RawItem $RawItem -MaxLength $Options.MaxLength -Source $Options.Source -DataFormat $Options.DataFormat -Separator $Options.Separator + } + else { + $outString = ConvertTo-PodeSyslogFormat -RawItem $RawItem -RFC $Options.Format -Source $Options.Source + } + } + + } + # Write the item to the file + $outString | Out-File -FilePath $path -Encoding $Options.Encoding -Append -Force + + # Remove log files beyond the MaxDays retention period, ensuring this runs once a day + if (($Options.MaxDays -gt 0) -and ($Options.NextClearDown -lt [DateTime]::Now.Date)) { + $date = [DateTime]::Now.Date.AddDays(-$Options.MaxDays) + + $null = Get-ChildItem -Path $Options.Path -Filter '*.log' -Force | + Where-Object { $_.CreationTime -lt $date } | + Remove-Item $_ -Force + + $Options.NextClearDown = [DateTime]::Now.Date.AddDays(1) + } + } + catch { + Invoke-PodeHandleFailure -Message "Failed to log a message: $_" -FailureAction $Options.FailureAction + } + } + } } + } +} + +function ConvertTo-PodeSyslogFormat { + [CmdletBinding(DefaultParameterSetName = 'Custom')] + param( + [hashtable] + $RawItem, - # mask values - $item = ($item | Protect-PodeLogItem) + [Parameter(Mandatory = $true, ParameterSetName = 'RFC')] + [ValidateSet('RFC3164', 'RFC5424')] + [string] + $RFC, - # variables - $date = [DateTime]::Now.ToString('yyyy-MM-dd') + [string] + $Source, - # do we need to reset the fileId? - if ($options.Date -ine $date) { - $options.Date = $date - $options.FileId = 0 + [Parameter( ParameterSetName = 'Custom')] + [int] + $MaxLength, + + [Parameter( ParameterSetName = 'Custom')] + [string] + $DataFormat, + + [Parameter( ParameterSetName = 'Custom')] + [string] + $Separator = ' ' + + + ) + $MaxLength = -1 + # Mask values + if ($RawItem.Message) { + if ($RawItem.StackTrace) { + $message = "$($RawItem.Level.ToUpperInvariant()): $($RawItem.Message | Protect-PodeLogItem). Exception Type: $($RawItem.Category). Stack Trace: $($RawItem.StackTrace)" + } + else { + $message = ($RawItem.Message | Protect-PodeLogItem) + } + } + + # Map $Level to syslog severity + switch ($RawItem.Level) { + 'emergency' { $severity = 0; break } + 'alert' { $severity = 1; break } + 'critical' { $severity = 2; break } + 'error' { $severity = 3; break } + 'warning' { $severity = 4; break } + 'notice' { $severity = 5; break } + 'info' { $severity = 6; break } + 'informational' { $severity = 6; break } + 'debug' { $severity = 7; break } + default { $severity = 6 } # Default to Informational + } + + # Define the facility and severity + $facility = 1 # User-level messages + $priority = ($facility * 8) + $severity + + # Determine the syslog message format + switch ($RFC) { + 'RFC3164' { + # Set the max message length per RFC 3164 section 4.1 + $MaxLength = 1024 + # Assemble the full syslog formatted message + $timestamp = $RawItem.Date.ToString('MMM dd HH:mm:ss') + $fullSyslogMessage = "<$priority>$timestamp $($PodeContext.Server.ComputerName) $Source[$processId]: $message" + break } + 'RFC5424' { + $processId = $PID + $timestamp = $RawItem.Date.ToString('yyyy-MM-ddTHH:mm:ss.ffffffK') - # get the fileId - if ($options.FileId -eq 0) { - $path = [System.IO.Path]::Combine($options.Path, "$($options.Name)_$($date)_*.log") - $options.FileId = (@(Get-ChildItem -Path $path)).Length - if ($options.FileId -eq 0) { - $options.FileId = 1 + # Assemble the full syslog formatted message + $fullSyslogMessage = "<$priority>1 $timestamp $($PodeContext.Server.ComputerName) $Source $processId - - $message" + + # Set the max message length per RFC 5424 section 6.1 + $MaxLength = 2048 + + break + } + # Simple version + default { + if ($DataFormat) { + $timestamp = $RawItem.Date.ToString($DataFormat) + } + else { + $timestamp = $DataFormat + } + # Assemble the full syslog formatted message + $fullSyslogMessage = "$timestamp$Separator$($RawItem.Level)$Separator$Source$Separator$message" + # Set the max message length + if ($Options.MaxLength) { + $MaxLength = $Options.MaxLength } + } + } + # Ensure that the message is not too long + if ($MaxLength -gt 0 -and $fullSyslogMessage.Length -gt $MaxLength) { + return $fullSyslogMessage.Substring(0, $MaxLength) + } + # Return the full syslog formatted message + return $fullSyslogMessage +} - $id = "$($options.FileId)".PadLeft(3, '0') - if ($options.MaxSize -gt 0) { - $path = [System.IO.Path]::Combine($options.Path, "$($options.Name)_$($date)_$($id).log") - if ((Get-Item -Path $path -Force).Length -ge $options.MaxSize) { - $options.FileId++ - $id = "$($options.FileId)".PadLeft(3, '0') +<# +.SYNOPSIS + Handles the sending of log messages to a Syslog server using various transport protocols. + +.DESCRIPTION + This function defines the logic for sending log messages to a Syslog server using different transport protocols including UDP, TCP, TLS, Splunk, and VMware LogInsight. + It supports both RFC 3164 and RFC 5424 formats and includes error handling based on user-defined actions. + +.NOTES + This is an internal function and may change in future releases of Pode. +#> +function Get-PodeLoggingSysLogMethod { + return { + param($MethodId) + + # Helper function to sanitize and return a default value if the input is null or whitespace + function sg($value) { + if ([string]::IsNullOrWhiteSpace($value)) { + return '-' } + return $value } - # get the file to write to - $path = [System.IO.Path]::Combine($options.Path, "$($options.Name)_$($date)_$($id).log") + # Set the name of the default runspace + ([System.Management.Automation.Runspaces.Runspace]::DefaultRunspace).Name = "LoggingSysLogMethod_$MethodId" + $log = @{} + $socketCreated = $false + try { + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + Start-Sleep -Milliseconds 100 + + if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { - # write the item to the file - $item.ToString() | Out-File -FilePath $path -Encoding utf8 -Append -Force + $Options = $log.Options + $RawItem = $log.RawItem - # if set, remove log files beyond days set (ensure this is only run once a day) - if (($options.MaxDays -gt 0) -and ($options.NextClearDown -lt [DateTime]::Now.Date)) { - $date = [DateTime]::Now.Date.AddDays(-$options.MaxDays) + if ($RawItem -isnot [array]) { + $RawItem = @($RawItem) + } - $null = Get-ChildItem -Path $options.Path -Filter '*.log' -Force | - Where-Object { $_.CreationTime -lt $date } | - Remove-Item $_ -Force + # Create the socket if it hasn't been created already + if (!$socketCreated) { + switch ($Options.Transport.ToUpperInvariant()) { + 'UDP' { + $udpClient = [System.Net.Sockets.UdpClient]::new() + } + 'TCP' { + # Create a TCP client for non-secure communication + $tcpClient = [System.Net.Sockets.TcpClient]::new() + $tcpClient.Connect($Options.Server, $Options.Port) + $networkStream = $tcpClient.GetStream() + } + 'TLS' { + # Create a TCP client for secure communication + $tcpClient = [System.Net.Sockets.TcpClient]::new() + $tcpClient.Connect($Options.Server, $Options.Port) + + $sslStream = if ($Options.SkipCertificateCheck) { + [System.Net.Security.SslStream]::new($tcpClient.GetStream(), $false, { $true }) + } + else { + [System.Net.Security.SslStream]::new($tcpClient.GetStream(), $false) + } + + # Define the TLS protocol version + $tlsProtocol = if ($Options.TlsProtocols) { + $Options.TlsProtocols + } + else { + [System.Security.Authentication.SslProtocols]::Tls12 # Default to TLS 1.2 + } + + # Authenticate as client with specific TLS protocol + $sslStream.AuthenticateAsClient($Options.Server, $null, $tlsProtocol, $false) + } + default { + $udpClient = [System.Net.Sockets.UdpClient]::new() + } + } + $socketCreated = $true + } - $options.NextClearDown = [DateTime]::Now.Date.AddDays(1) + for ($i = 0; $i -lt $RawItem.Length; $i++) { + $fullSyslogMessage = ConvertTo-PodeSyslogFormat -RawItem $RawItem[$i] -RFC $Options.SyslogProtocol -Source $Options.Source + # Convert the message to a byte array + $byteMessage = $($Options.Encoding).GetBytes($fullSyslogMessage) + + # Determine the transport protocol and send the message + switch ($Options.Transport.ToUpperInvariant()) { + 'UDP' { + try { + # Send the message to the syslog server + $udpClient.Send($byteMessage, $byteMessage.Length, $Options.Server, $Options.Port) + } + catch { + Invoke-PodeHandleFailure -Message "Failed to send UDP message: $_" -FailureAction $Options.FailureAction + } + } + 'TCP' { + try { + # Send the message + $networkStream.Write($byteMessage, 0, $byteMessage.Length) + $networkStream.Flush() + } + catch { + Invoke-PodeHandleFailure -Message "Failed to send TCP message: $_" -FailureAction $Options.FailureAction + } + } + 'TLS' { + try { + # Send the message + $sslStream.Write($byteMessage) + $sslStream.Flush() + } + catch { + Invoke-PodeHandleFailure -Message "Failed to send secure TLS message: $_" -FailureAction $Options.FailureAction + } + } + } + } + } + } + } + finally { + # Close the sockets and cleanup + switch ($Options.Transport.ToUpperInvariant()) { + 'UDP' { + # Close the UDP client + if ($udpClient) { + $udpClient.Close() + } + } + 'TCP' { + # Close the TCP client + if ($networkStream) { $networkStream.Close() } + if ($tcpClient) { $tcpClient.Close() } + } + 'TLS' { + # Close the TCP client + if ($sslStream) { $sslStream.Close() } + if ($tcpClient) { $tcpClient.Close() } + } + } + $socketCreated = $false } } } -function Get-PodeLoggingEventViewerMethod { +<# +.SYNOPSIS +Defines the method for sending log messages to a Restful API endpoint. + +.DESCRIPTION +This internal function handles the sending of log messages to Restful API endpoints for platforms like Splunk and Log Insight. It formats log messages, manages headers, and includes error handling based on user-defined actions. + +.NOTES +This is an internal function and may change in future releases of Pode. +#> +function Get-PodeLoggingRestfulMethod { return { - param($item, $options, $rawItem) + param($MethodId) - if ($item -isnot [array]) { - $item = @($item) - } + # Set the name of the default runspace + ([System.Management.Automation.Runspaces.Runspace]::DefaultRunspace).Name = "LoggingRestfulMethod_$MethodId" + $log = @{} + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + Start-Sleep -Milliseconds 100 + + if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { + if ($null -ne $log) { + $Item = $log.Item + $Options = $log.Options + $RawItem = $log.RawItem + + # Ensure item and rawItem are arrays + if ($Item -isnot [array]) { + $Item = @($Item) + } - if ($rawItem -isnot [array]) { - $rawItem = @($rawItem) + if ($RawItem -isnot [array]) { + $RawItem = @($RawItem) + } + + # Determine the transport protocol and send the message + switch ($Options.Platform) { + 'Splunk' { + # Construct the Splunk API URL + $url = "$($Options.BaseUrl)/services/collector" + + # Set the headers for Splunk + $headers = @{ + 'Authorization' = "Splunk $($Options.Token)" + 'Content-Type' = 'application/json' + } + + $items = @() + for ($i = 0; $i -lt $Item.Length; $i++) { + # Mask values + $message = ($Item[$i] | Protect-PodeLogItem) + if ([string]::IsNullOrWhiteSpace($RawItem[$i].Level)) { + $severity = 'INFO' + } + else { + $severity = $RawItem[$i].Level.ToUpperInvariant() + } + $items += ConvertTo-Json -Compress -InputObject @{ + event = $message + host = $PodeContext.Server.ComputerName + source = $Options.source + time = [math]::Round(($RawItem[$i].Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalSeconds) + fields = @{ + severity = $severity + } + } + } + + $body = $items -join ' ' + + # Send the message to Splunk + try { + Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -SkipCertificateCheck:$Options.SkipCertificateCheck + } + catch { + Invoke-PodeHandleFailure -Message "Failed to send log to Splunk: $_" -FailureAction $Options.FailureAction + } + + break + } + + 'LogInsight' { + # Construct the Log Insight API URL + $url = "$($Options.BaseUrl)/api/v1/messages/ingest/$($Options.Id)" + + # Set the headers for Log Insight + $headers = @{ + 'Content-Type' = 'application/json' + } + $messages = @() + for ($i = 0; $i -lt $Item.Length; $i++) { + $messages += @{ + text = ($Item[$i] | Protect-PodeLogItem) + timestamp = [math]::Round(($RawItem[$i].Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalSeconds) + } + } + + # Define the message payload + $payload = @{ + messages = $messages + } + + # Convert payload to JSON + $body = $payload | ConvertTo-Json -Compress + + # Send the message to Log Insight + try { + Invoke-RestMethod -Uri $url -Method Post -Body $body -Headers $headers -SkipCertificateCheck:$Options.SkipCertificateCheck + } + catch { + Invoke-PodeHandleFailure -Message "Failed to send log to LogInsight: $_" -FailureAction $Options.FailureAction + } + + break + } + } + } + } } + } +} - for ($i = 0; $i -lt $item.Length; $i++) { - # convert log level - info if no level present - $entryType = ConvertTo-PodeEventViewerLevel -Level $rawItem[$i].Level +<# +.SYNOPSIS +Defines the method for sending log messages to the Windows Event Viewer. - # create log instance - $entryInstance = [System.Diagnostics.EventInstance]::new($options.ID, 0, $entryType) +.DESCRIPTION +This internal function handles the sending of log messages to the Windows Event Viewer, converting log levels and creating event log entries. It includes error handling based on user-defined actions. - # create event log - $entryLog = [System.Diagnostics.EventLog]::new() - $entryLog.Log = $options.LogName - $entryLog.Source = $options.Source +.NOTES +This is an internal function and may change in future releases of Pode. +#> +function Get-PodeLoggingEventViewerMethod { + return { + param($MethodId) - try { - $message = ($item[$i] | Protect-PodeLogItem) - $entryLog.WriteEvent($entryInstance, $message) - } - catch { - $_ | Write-PodeErrorLog -Level Debug + # Set the name of the default runspace + ([System.Management.Automation.Runspaces.Runspace]::DefaultRunspace).Name = "LoggingEventViewerMethod_$MethodId" + $log = @{} + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + Start-Sleep -Milliseconds 100 + + if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { + if ($null -ne $log) { + $Item = $log.Item + $Options = $log.Options + $RawItem = $log.RawItem + + # Ensure item and rawItem are arrays + if ($Item -isnot [array]) { + $Item = @($Item) + } + + if ($RawItem -isnot [array]) { + $RawItem = @($RawItem) + } + + for ($i = 0; $i -lt $RawItem.Length; $i++) { + # Convert log level to Event Viewer entry type - default to 'Information' if no level present + $entryType = ConvertTo-PodeEventViewerLevel -Level $RawItem[$i].Level + + # Create EventInstance for the log entry + $entryInstance = [System.Diagnostics.EventInstance]::new($Options.ID, 0, $entryType) + + # Create EventLog object and set the log name and source + $entryLog = [System.Diagnostics.EventLog]::new() + $entryLog.Log = $Options.LogName + $entryLog.Source = $Options.Source + + try { + # Mask values and write the event to the Event Viewer + $message = ($Item[$i] | Protect-PodeLogItem) + $entryLog.WriteEvent($entryInstance, $message) + } + catch { + Invoke-PodeHandleFailure -Message "Failed to write an Event Viewer message: $_" -FailureAction $Options.FailureAction + } + } + } } } } } +<# +.SYNOPSIS +Converts a log level string to a corresponding EventLogEntryType. + +.DESCRIPTION +This internal function converts a provided log level string to the corresponding `System.Diagnostics.EventLogEntryType` enumeration value. +It defaults to `Information` if the level is empty or unrecognized. + +.PARAMETER Level +The log level string to be converted (e.g., 'error', 'warning'). + +.RETURNS +Returns a `System.Diagnostics.EventLogEntryType` enumeration value corresponding to the provided log level. + +.NOTES +This is an internal function and may change in future releases of Pode. +#> function ConvertTo-PodeEventViewerLevel { param( [Parameter()] @@ -132,10 +645,26 @@ function ConvertTo-PodeEventViewerLevel { return [System.Diagnostics.EventLogEntryType]::Information } +<# +.SYNOPSIS +Gets the script block for a specified inbuilt logging type. + +.DESCRIPTION +This function returns a script block that formats log entries for a specified inbuilt logging type in Pode. The supported types are 'Errors', 'Requests', 'General', and 'Main'. Each type has its own formatting logic. + +.PARAMETER Type +The type of logging to get the script block for. Must be one of 'Errors', 'Requests', 'General', or 'Main'. + +.EXAMPLE +$script = Get-PodeLoggingInbuiltType -Type 'Requests' + +.EXAMPLE +$script = Get-PodeLoggingInbuiltType -Type 'Errors' +#> function Get-PodeLoggingInbuiltType { param( [Parameter(Mandatory = $true)] - [ValidateSet('Errors', 'Requests')] + [ValidateSet('Errors', 'Requests', 'General', 'Main', 'Listener')] [string] $Type ) @@ -145,24 +674,42 @@ function Get-PodeLoggingInbuiltType { $script = { param($item, $options) - # just return the item if Raw is set + # Just return the item if Raw is set if ($options.Raw) { return $item } + # Helper function to sanitize and return a default value if the input is null or whitespace function sg($value) { if ([string]::IsNullOrWhiteSpace($value)) { return '-' } - return $value } - # build the url with http method - $url = "$(sg $item.Request.Method) $(sg $item.Request.Resource) $(sg $item.Request.Protocol)" - - # build and return the request row - return "$(sg $item.Host) $(sg $item.RfcUserIdentity) $(sg $item.User) [$(sg $item.Date)] `"$($url)`" $(sg $item.Response.StatusCode) $(sg $item.Response.Size) `"$(sg $item.Request.Referrer)`" `"$(sg $item.Request.Agent)`"" + switch ($options.LogFormat.ToLowerInvariant()) { + 'extended' { + return "$($item.Date.ToString('yyyy-MM-dd')) $($item.Date.ToString('HH:mm:ss')) $(sg $item.Host) $(sg $item.User) $(sg $item.Request.Method) $(sg $item.Request.Resource) $(sg $item.Request.Query) $(sg $item.Response.StatusCode) $(sg $item.Response.Size) `"$(sg $item.Request.Agent)`"" + } + 'common' { + # Build the URL with HTTP method + $url = "$(sg $item.Request.Method) $(sg $item.Request.Resource) $(sg $item.Request.Protocol)" + $date = [regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2') + return "$(sg $item.Host) $(sg $item.RfcUserIdentity) $(sg $item.User) [$date] `"$($url)`" $(sg $item.Response.StatusCode) $(sg $item.Response.Size)" + } + 'json' { + return "{`"time`": `"$($item.Date.ToString('yyyy-MM-ddTHH:mm:ssK'))`",`"remote_ip`": `"$(sg $item.Host)`",`"user`": `"$(sg $item.User)`",`"method`": `"$(sg $item.Request.Method)`",`"uri`": `"$(sg $item.Request.Resource)`",`"query`": `"$(sg $item.Request.Query)`",`"status`": $(sg $item.Response.StatusCode),`"response_size`": $(sg $item.Response.Size),`"user_agent`": `"$(sg $item.Request.Agent)`"}" + } + # Combined is the default + default { + # Build the URL with HTTP method + $url = "$(sg $item.Request.Method) $(sg $item.Request.Resource) $(sg $item.Request.Protocol)" + $date = [regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2') + # Build and return the request row + return "$(sg $item.Host) $(sg $item.RfcUserIdentity) $(sg $item.User) [$date] `"$($url)`" $(sg $item.Response.StatusCode) $(sg $item.Response.Size) `"$(sg $item.Request.Referrer)`" `"$(sg $item.Request.Agent)`"" + } + } + return $item } } @@ -170,19 +717,19 @@ function Get-PodeLoggingInbuiltType { $script = { param($item, $options) - # do nothing if the error level isn't present + # Do nothing if the error level isn't present if (@($options.Levels) -inotcontains $item.Level) { return } - # just return the item if Raw is set + # Just return the item if Raw is set if ($options.Raw) { return $item } - # build the exception details + # Build the exception details $row = @( - "Date: $($item.Date.ToString('yyyy-MM-dd HH:mm:ss'))", + "Date: $($item.Date.ToString($options.DataFormat))", "Level: $($item.Level)", "ThreadId: $($item.ThreadId)", "Server: $($item.Server)", @@ -190,24 +737,101 @@ function Get-PodeLoggingInbuiltType { "Message: $($item.Message)", "StackTrace: $($item.StackTrace)" ) - - # join the details and return + # Join the details and return return "$($row -join "`n")`n" } } + 'general' { + $script = { + param($item, $options) + + # Do nothing if the error level isn't present + if (@($options.Levels) -inotcontains $item.Level) { + return + } + + # Just return the item if Raw is set + if ($options.Raw) { + return $item + } + + return "[$($item.Date.ToString($options.DataFormat))] $($item.Level) $( $item.Tag) $($item.ThreadId) $($item.Message)" + } + } + + 'main' { + $script = { + param($item, $options) + + # Just return the item if Raw is set + if ($options.Raw) { + return $item + } + + return "[$($item.Date.ToString($options.DataFormat))] $($item.Level) $( $item.Tag) $($item.ThreadId) $($item.Message)" + } + } } return $script } +<# +.SYNOPSIS +Gets the name of the request logger. + +.DESCRIPTION +This function returns the name of the logger used for logging requests in Pode. + +.RETURNS +[string] - The name of the request logger. + +.EXAMPLE +Get-PodeRequestLoggingName +#> function Get-PodeRequestLoggingName { + # Return the name of the request logger return '__pode_log_requests__' } + +<# +.SYNOPSIS +Gets the name of the error logger. + +.DESCRIPTION +This function returns the name of the logger used for logging errors in Pode. + +.RETURNS +[string] - The name of the error logger. + +.EXAMPLE +Get-PodeErrorLoggingName +#> function Get-PodeErrorLoggingName { + # Return the name of the error logger return '__pode_log_errors__' } +<# +.SYNOPSIS +Gets the name of the main logger. + +.DESCRIPTION +This function returns the name of the main logger used in Pode. + +.RETURNS +[string] - The name of the main logger. + +.EXAMPLE +Get-PodeMainLoggingName +#> +function Get-PodeMainLoggingName { + # Return the name of the main logger + return '__pode_log_main__' +} + + <# .SYNOPSIS Retrieves a Pode logger by name. @@ -233,19 +857,70 @@ function Get-PodeLogger { $Name ) - return $PodeContext.Server.Logging.Types[$Name] + return $PodeContext.Server.Logging.Type[$Name] } -function Test-PodeLoggerEnabled { +<# +.SYNOPSIS +Tests if a specified logger is a standard logger. + +.DESCRIPTION +This function checks if the specified logger is configured as a standard logger in the Pode context. + +.PARAMETER Name +The name of the logger to test. + +.OUTPUTS +[bool] - Returns $true if the logger is a standard logger, otherwise $false. + +.EXAMPLE +Test-PodeStandardLogger -Name 'MyLogger' +#> +function Test-PodeStandardLogger { + [CmdletBinding()] + [OutputType([bool])] param( [Parameter(Mandatory = $true)] [string] $Name ) - return ($PodeContext.Server.Logging.Enabled -and $PodeContext.Server.Logging.Types.ContainsKey($Name)) + # Check if the specified logger is a standard logger + return $PodeContext.Server.Logging.Type[$Name].Standard } +<# +.SYNOPSIS +Determines if a specified logger is enabled. + +.DESCRIPTION +This function checks if a specified logger is enabled by verifying if logging is enabled in the Pode context and if the logger exists within the logging configuration. + +.PARAMETER Name +The name of the logger to check. + +.EXAMPLE +Test-PodeLoggerEnabled -Name 'MyLogger' + +# This command checks if the logger named 'MyLogger' is enabled. +#> +function Test-PodeLoggerEnabled { + param( + [string] + $Name + ) + + if ($Name) { + # Check if logging is enabled and if the specified logger exists + return ([pode.PodeLogger]::Enabled -and $PodeContext -and $PodeContext.Server.Logging.Type.ContainsKey($Name)) + } + else { + # Check if logging is generally enabled + return [pode.PodeLogger]::Enabled + } +} + + <# .SYNOPSIS Gets the error logging levels for Pode. @@ -266,14 +941,56 @@ function Get-PodeErrorLoggingLevel { return (Get-PodeLogger -Name (Get-PodeErrorLoggingName)).Arguments.Levels } +<# +.SYNOPSIS +Tests if error logging is enabled. + +.DESCRIPTION +This function checks if error logging is enabled by testing the logger configuration for error logging. + +.EXAMPLE +Test-PodeErrorLoggingEnabled +#> function Test-PodeErrorLoggingEnabled { + # Get the name of the error logger and test if it is enabled return (Test-PodeLoggerEnabled -Name (Get-PodeErrorLoggingName)) } +<# +.SYNOPSIS +Tests if request logging is enabled. + +.DESCRIPTION +This function checks if request logging is enabled by testing the logger configuration for request logging. + +.EXAMPLE +Test-PodeRequestLoggingEnabled +#> function Test-PodeRequestLoggingEnabled { + # Get the name of the request logger and test if it is enabled return (Test-PodeLoggerEnabled -Name (Get-PodeRequestLoggingName)) } + +<# +.SYNOPSIS +Writes a log entry for a Pode web request. + +.DESCRIPTION +This function writes a log entry for a Pode web request. It logs details about the request and response, including method, resource, status code, and user information. The log entry is enqueued for processing if logging is enabled. + +.PARAMETER Request +The Pode web request object. + +.PARAMETER Response +The Pode web response object. + +.PARAMETER Path +The path of the request. + +.EXAMPLE +Write-PodeRequestLog -Request $webEvent.Request -Response $webEvent.Response -Path $webEvent.Path +#> function Write-PodeRequestLog { param( [Parameter(Mandatory = $true)] @@ -287,38 +1004,48 @@ function Write-PodeRequestLog { $Path ) - # do nothing if logging is disabled, or request logging isn't setup + # Do nothing if logging is disabled, or request logging isn't set up $name = Get-PodeRequestLoggingName if (!(Test-PodeLoggerEnabled -Name $name)) { return } - # build a request object + # Determine the current date and time, respecting the AsUTC setting + if ($PodeContext.Server.Logging.Type[$Name].Method.Arguments.AsUTC) { + $date = [datetime]::UtcNow + } + else { + $date = [datetime]::Now + } + + # Build a request object $item = @{ Host = $Request.RemoteEndPoint.Address.IPAddressToString RfcUserIdentity = '-' User = '-' - Date = [DateTime]::Now.ToString('dd/MMM/yyyy:HH:mm:ss zzz') + Date = $date Request = @{ Method = $Request.HttpMethod.ToUpperInvariant() Resource = $Path Protocol = "HTTP/$($Request.ProtocolVersion)" Referrer = $Request.UrlReferrer Agent = $Request.UserAgent + Query = ($Request.url -split '\?')[1] } Response = @{ StatusCode = $Response.StatusCode StatusDescription = $Response.StatusDescription Size = '-' } + Level = 'info' } - # set size if >0 + # Set size if >0 if ($Response.ContentLength64 -gt 0) { $item.Response.Size = $Response.ContentLength64 } - # set username - dot spaces + # Set username - dot spaces if (Test-PodeAuthUser -IgnoreSession) { $userProps = (Get-PodeLogger -Name $name).Properties.Username.Split('.') @@ -332,13 +1059,27 @@ function Write-PodeRequestLog { } } - # add the item to be processed - $null = $PodeContext.LogsToProcess.Add(@{ + # Add the item to be processed + $null = [Pode.PodeLogger]::Enqueue(@{ Name = $name Item = $item }) } + +<# +.SYNOPSIS +Adds request logging endware to a Pode web event. + +.DESCRIPTION +This function adds endware to a Pode web event for logging request and response details. It checks if request logging is enabled and configured before attaching the logging logic to the web event's end handler. + +.PARAMETER WebEvent +The Pode web event to which the logging endware will be added. + +.EXAMPLE +Add-PodeRequestLogEndware -WebEvent $webEvent +#> function Add-PodeRequestLogEndware { param( [Parameter(Mandatory = $true)] @@ -346,13 +1087,13 @@ function Add-PodeRequestLogEndware { $WebEvent ) - # do nothing if logging is disabled, or request logging isn't setup + # Do nothing if logging is disabled, or request logging isn't set up $name = Get-PodeRequestLoggingName if (!(Test-PodeLoggerEnabled -Name $name)) { return } - # add the request logging endware + # Add the request logging endware $WebEvent.OnEnd += @{ Logic = { Write-PodeRequestLog -Request $WebEvent.Request -Response $WebEvent.Response -Path $WebEvent.Path @@ -360,85 +1101,153 @@ function Add-PodeRequestLogEndware { } } +<# +.SYNOPSIS +Tests if any loggers are configured or if logging is enabled. + +.DESCRIPTION +This function checks if any loggers are configured or if logging is enabled within the Pode context. It returns a boolean value indicating the presence of configured loggers or the status of logging. + +.EXAMPLE +Test-PodeLoggersExist +#> function Test-PodeLoggersExist { - if (($null -eq $PodeContext.Server.Logging) -or ($null -eq $PodeContext.Server.Logging.Types)) { + # Check if the logging context or logging types are null + if (($null -eq $PodeContext.Server.Logging) -or ($null -eq $PodeContext.Server.Logging.Type)) { return $false } - return (($PodeContext.Server.Logging.Types.Count -gt 0) -or ($PodeContext.Server.Logging.Enabled)) + # Return true if there are any logging types configured or if logging is enabled + return (($PodeContext.Server.Logging.Type.Count -gt 0) -or ($PodeContext.Server.Logging.Enabled)) } -function Start-PodeLoggingRunspace { - # skip if there are no loggers configured, or logging is disabled +<# +.SYNOPSIS +Starts the Pode logger dispatcher which processes and dispatches log entries. + +.DESCRIPTION +This function initializes and starts a logger dispatcher runspace that processes log entries from a queue and dispatches them to the appropriate logging methods. It handles batching of log entries and ensures that log entries are processed in a timely manner. + +.EXAMPLE +Start-PodeLoggerDispatcher +#> +function Start-PodeLoggerDispatcher { + # Skip if there are no loggers configured, or logging is disabled if (!(Test-PodeLoggersExist)) { return } - $script = { - while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { - # if there are no logs to process, just sleep for a few seconds - but after checking the batch - if ($PodeContext.LogsToProcess.Count -eq 0) { - Test-PodeLoggerBatch - Start-Sleep -Seconds 5 - continue - } + $scriptBlock = { + ([System.Management.Automation.Runspaces.Runspace]::DefaultRunspace).Name = 'LoggerDispatcher' - # safely pop off the first log from the array - $log = (Lock-PodeObject -Return -Object $PodeContext.LogsToProcess -ScriptBlock { - $log = $PodeContext.LogsToProcess[0] - $null = $PodeContext.LogsToProcess.RemoveAt(0) - return $log - }) + $log = @{} + # Wait for the server to start before processing logs + if ( Wait-PodeServerToStart) { + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + # Check if the log queue has reached its limit + if ([Pode.PodeLogger]::Count -ge $PodeContext.Server.Logging.QueueLimit) { + Invoke-PodeHandleFailure -Message "Reached the log Queue Limit of $($PodeContext.Server.Logging.QueueLimit)" -FailureAction $logger.Method.Arguments.FailureAction + } - # run the log item through the appropriate method - $logger = Get-PodeLogger -Name $log.Name - $now = [datetime]::Now + # Try to dequeue a log entry from the queue + if ( [Pode.PodeLogger]::TryDequeue([ref]$log)) { + # If the log is null, check batch then sleep and skip + if ($null -eq $log) { + Start-Sleep -Milliseconds 100 + continue + } + if ($log.Name -eq 'Listener') { - # if the log is null, check batch then sleep and skip - if ($null -eq $log) { - Start-Sleep -Milliseconds 100 - continue - } + if ($log.Item -is [System.Exception]) { - # convert to log item into a writable format - $rawItems = $log.Item - $_args = @($log.Item) + @($logger.Arguments) - $result = @(Invoke-PodeScriptBlock -ScriptBlock $logger.ScriptBlock -Arguments $_args -UsingVariables $logger.UsingVariables -Return -Splat) - - # check batching - $batch = $logger.Method.Batch - if ($batch.Size -gt 1) { - # add current item to batch - $batch.Items += $result - $batch.RawItems += $log.Item - $batch.LastUpdate = $now - - # if the current amount of items matches the batch, write - $result = $null - if ($batch.Items.Length -ge $batch.Size) { - $result = $batch.Items - $rawItems = $batch.RawItems - } + Write-PodeErrorLog -Exception $log.Item -Level = 'Error' -ThreadId $log.Item.ThreadId + } + else { + Write-PodeLog -Name (Get-PodeErrorLoggingName) -Message $log.Item.Message -Level 'error' -ThreadId $log.Item.ThreadId -Tag 'Listener' + + } + continue + } + # Run the log item through the appropriate method + $logger = $PodeContext.Server.Logging.Type[$log.Name] + $now = [datetime]::Now + + # Convert the log item into a writable format + $rawItem = $log.Item + $_args = @($log.Item) + @($logger.Arguments) + + $item = @(Invoke-PodeScriptBlock -ScriptBlock $logger.ScriptBlock -Arguments $_args -UsingVariables $logger.UsingVariables -Return -Splat) + + # Check batching + $batch = $logger.Method.Batch + if ($batch.Size -gt 1) { + # Add current item to batch + $batch.Items += $item + $batch.RawItems += $log.Item + $batch.LastUpdate = $now + + # If the current amount of items matches the batch size, write + $item = $null + if ($batch.Items.Length -ge $batch.Size) { + $item = $batch.Items + $rawItem = $batch.RawItems + } + + # If we're writing, reset the items + if ($null -ne $item) { + $batch.Items = @() + $batch.RawItems = @() + } + } - # if we're writing, reset the items - if ($null -ne $result) { - $batch.Items = @() - $batch.RawItems = @() + # Send the writable log item off to the log writer + if ($null -ne $item) { + foreach ($method in $logger.Method) { + if ($method.NoRunspace) { + # Legacy for custom methods + # $null = Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Logging.Method[$method.Id].ScriptBlock -Arguments $_args -UsingVariables $method.UsingVariables -Splat + $_args = @(, $item) + @($method.Arguments) + @(, $rawItem) + $null = Invoke-PodeScriptBlock -ScriptBlock $logger.Method.ScriptBlock -Arguments $_args -UsingVariables $logger.Method.UsingVariables -Splat + } + else { + $_args = @{ + Item = $item + Options = $method.Arguments + RawItem = $rawItem + } + $PodeContext.Server.Logging.Method[$method.Id].Queue.Enqueue($_args) + } + } + } + + # Small sleep to lower CPU usage + Start-Sleep -Milliseconds 100 + } + else { + # Check the logger batch + Test-PodeLoggerBatch + Start-Sleep -Seconds 5 } } + } + } - # send the writable log item off to the log writer - if ($null -ne $result) { - $_args = @(, $result) + @($logger.Method.Arguments) + @(, $rawItems) - $null = Invoke-PodeScriptBlock -ScriptBlock $logger.Method.ScriptBlock -Arguments $_args -UsingVariables $logger.Method.UsingVariables -Splat + # Retrieve unique method IDs + $uniqueMethodIds = ($PodeContext.Server.Logging.Type.values.Method.Id | Select-Object -Unique) + if ($uniqueMethodIds.Count -gt 0) { + # Set maximum runspaces for the logs pool + if ($PodeContext.RunspacePools['logs'].Pool.SetMaxRunspaces($uniqueMethodIds.Count + 1)) { + foreach ($methodId in $uniqueMethodIds) { + if ($null -ne $PodeContext.Server.Logging.Method[$methodId]) { + $PodeContext.Server.Logging.Method[$methodId].Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + $PodeContext.Server.Logging.Method[$methodId].Runspace = Add-PodeRunspace -PassThru -Type Logs -ScriptBlock $PodeContext.Server.Logging.Method[$methodId].ScriptBlock -Parameters @{ MethodId = $methodId } + } } - - # small sleep to lower cpu usage - Start-Sleep -Milliseconds 100 } } - Add-PodeRunspace -Type Main -ScriptBlock $script + # Add the logger dispatcher runspace + Add-PodeRunspace -Type Logs -ScriptBlock $scriptBlock } <# @@ -455,7 +1264,7 @@ function Test-PodeLoggerBatch { $now = [datetime]::Now # check each logger, and see if its batch needs to be written - foreach ($logger in $PodeContext.Server.Logging.Types.Values) { + foreach ($logger in $PodeContext.Server.Logging.Type.Values) { $batch = $logger.Method.Batch if (($batch.Size -gt 1) -and ($batch.Items.Length -gt 0) -and ($batch.Timeout -gt 0) ` -and ($null -ne $batch.LastUpdate) -and ($batch.LastUpdate.AddSeconds($batch.Timeout) -le $now) @@ -470,4 +1279,117 @@ function Test-PodeLoggerBatch { $null = Invoke-PodeScriptBlock -ScriptBlock $logger.Method.ScriptBlock -Arguments $_args -UsingVariables $logger.Method.UsingVariables -Splat } } +} + +<# +.SYNOPSIS + Writes an entry to the Pode trace log. + +.DESCRIPTION + Writes an entry to the Pode trace log. The entry can be based on either an operation and its parameters or a custom message. + This function handles the formatting of log entries and enqueues them for processing. + +.PARAMETER Operation + The operation being logged. + +.PARAMETER Parameters + A hashtable of parameters associated with the operation. + +.PARAMETER Message + A custom message to log. + +.EXAMPLE + Write-PodeTraceLog -Operation 'Remove-PodeLogger' -Parameters @{ Name = 'LogName' } + +.EXAMPLE + Write-PodeTraceLog -Message 'Custom log message.' +#> +function Write-PodeTraceLog { + [CmdletBinding(DefaultParameterSetName = 'Parameter')] + param( + [Parameter(Mandatory, ParameterSetName = 'Parameter')] + [string] + $Operation, + + [Parameter(Mandatory, ParameterSetName = 'Parameter')] + [hashtable] + $Parameters, + + [Parameter(Mandatory, ParameterSetName = 'Message')] + [string] + $Message + ) + + # Do nothing if logging is disabled, or error logging isn't set up + $name = Get-PodeMainLoggingName + if (!(Test-PodeLoggerEnabled -Name $name)) { + return + } + + # Determine the parameter set and build the log message + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'parameter' { + $Message = if ($Parameters) { + $paramString = ($Parameters.GetEnumerator() | ForEach-Object { + if ($_.Value -is [scriptblock]) { + "$($_.Key)=" + } + elseif ($_.Key -eq 'ArgumentList') { + "$($_.Key)=" + } + elseif ($_.Key -eq 'Route') { + "$($_.Key)={ Path : `"$($_.Value.Path -join ',')`" ,Method : `"$($_.Value.Method -join ',')`" }" + } + elseif ($_.Key -eq 'ExternalDoc') { + "$($_.Key)=$($_.Value|ConvertTo-Json -Compress)" + } + elseif ($_.Key -eq 'Scheme') { + "$($_.Key)={ Name : `"$($_.Value.Name)`" , Scheme : `"$($_.Value.Scheme)`" }" + } + elseif ($_.Key -eq 'InputObject') { + "$($_.Key)=" + } + else { + "$($_.Key)=$($_.Value)" + } + }) -join ', ' + "Operation $Operation invoked with parameters: $paramString" + } + else { + "Operation $Operation invoked with no parameters" + } + break + } + 'Message' { + $Operation = '-' + $Parameters = @{} + break + } + } + + # Determine the current date and time, respecting the AsUTC setting + if ($PodeContext.Server.Logging.Type[$Name].Method.Arguments.AsUTC) { + $date = [datetime]::UtcNow + } + else { + $date = [datetime]::Now + } + + # Build the log item + $item = @{ + Parameters = $Parameters + Message = $Message + Operation = $Operation + Level = 'Info' + Server = $PodeContext.Server.ComputerName + Tag = 'Main' + Date = $date + ThreadId = [System.Threading.Thread]::CurrentThread.ManagedThreadId + } + + # Add the log item to the processing queue + $null = [Pode.PodeLogger]::Enqueue(@{ + Name = $name + Item = $item + }) } \ No newline at end of file diff --git a/src/Private/Server.ps1 b/src/Private/Server.ps1 index 82a971288..ccce3098b 100644 --- a/src/Private/Server.ps1 +++ b/src/Private/Server.ps1 @@ -64,7 +64,7 @@ function Start-PodeInternalServer { if (!$PodeContext.Server.IsServerless) { # start runspace for loggers - Start-PodeLoggingRunspace + Start-PodeLoggerDispatcher # start runspace for timers Start-PodeTimerRunspace @@ -147,11 +147,15 @@ function Start-PodeInternalServer { # run running event hooks Invoke-PodeEvent -Type Running + Write-PodeTraceLog -Message "Pode $(Get-PodeVersion) (PID: $($PID))" + # state what endpoints are being listened on if ($endpoints.Length -gt 0) { # Listening on the following $endpoints.Length endpoint(s) [$PodeContext.Threads.General thread(s)] - Write-PodeHost ($PodeLocale.listeningOnEndpointsMessage -f $endpoints.Length, $PodeContext.Threads.General) -ForegroundColor Yellow + $msg = ($PodeLocale.listeningOnEndpointsMessage -f $endpoints.Length, $PodeContext.Threads.General) + Write-PodeHost $msg -ForegroundColor Yellow + $endpoints | ForEach-Object { $flags = @() if ($_.DualMode) { @@ -166,7 +170,11 @@ function Start-PodeInternalServer { } Write-PodeHost "`t- $($_.Url) $($flags)" -ForegroundColor Yellow + $urlAndFlags += "$($_.Url) $($flags)" } + + Write-PodeTraceLog -Message "$msg - $($urlAndFlags -join ' , ')" + # state the OpenAPI endpoints for each definition foreach ($key in $PodeContext.Server.OpenAPI.Definitions.keys) { $bookmarks = $PodeContext.Server.OpenAPI.Definitions[$key].hiddenComponents.bookmarks @@ -206,7 +214,7 @@ function Start-PodeInternalServer { } } } - + $PodeContext.Server.Started = $true } } catch { @@ -246,7 +254,9 @@ function Restart-PodeInternalServer { $PodeContext.Server.Views.Clear() $PodeContext.Timers.Items.Clear() - $PodeContext.Server.Logging.Types.Clear() + $PodeContext.Server.Logging.Type.Clear() + $PodeContext.Server.Logging.Method.Clear() + Clear-PodeLogging # clear schedules $PodeContext.Schedules.Items.Clear() diff --git a/src/Public/Access.ps1 b/src/Public/Access.ps1 index 51febb3f1..a1c2a0afb 100644 --- a/src/Public/Access.ps1 +++ b/src/Public/Access.ps1 @@ -173,6 +173,9 @@ function Add-PodeAccess { $Match = 'One' ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # check name unique if (Test-PodeAccessExists -Name $Name) { throw ($PodeLocale.accessMethodAlreadyDefinedExceptionMessage -f $Name) #"Access method already defined: $($Name)" @@ -304,6 +307,9 @@ function Add-PodeAccessCustom { ) begin { + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $routes = @() } @@ -605,6 +611,9 @@ function Remove-PodeAccess { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $null = $PodeContext.Server.Authorisations.Methods.Remove($Name) } @@ -622,6 +631,9 @@ function Clear-PodeAccess { [CmdletBinding()] param() + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $PodeContext.Server.Authorisations.Methods.Clear() } @@ -663,6 +675,9 @@ function Add-PodeAccessMiddleware { $Route ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if (!(Test-PodeAccessExists -Name $Access)) { throw ($PodeLocale.accessMethodNotExistExceptionMessage -f $Access) #"Access method does not exist: $($Access)" } diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index ab9ff04c5..0404ce121 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -756,6 +756,9 @@ function Add-PodeAuth { $SuccessUseOrigin ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # ensure the name doesn't already exist if (Test-PodeAuthExists -Name $Name) { # Authentication method already defined: {0} @@ -1279,6 +1282,9 @@ function Add-PodeAuthWindowsAd { $KeepCredential ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # ensure the name doesn't already exist if (Test-PodeAuthExists -Name $Name) { # Authentication method already defined: {0} @@ -1419,6 +1425,9 @@ function Add-PodeAuthSession { $SuccessUseOrigin ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # if sessions haven't been setup, error if (!(Test-PodeSessionsEnabled)) { # Sessions have not been configured @@ -1512,6 +1521,9 @@ function Remove-PodeAuth { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $null = $PodeContext.Server.Authentications.Methods.Remove($Name) } @@ -1529,6 +1541,9 @@ function Clear-PodeAuth { [CmdletBinding()] param() + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $PodeContext.Server.Authentications.Methods.Clear() } @@ -1579,6 +1594,9 @@ function Add-PodeAuthMiddleware { $OADefinitionTag ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $DefinitionTag = Test-PodeOADefinitionTag -Tag $OADefinitionTag if (!(Test-PodeAuthExists -Name $Authentication)) { @@ -1706,6 +1724,9 @@ function Add-PodeAuthIIS { $SuccessUseOrigin ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # ensure we're on Windows! if (!(Test-PodeIsWindows)) { # IIS Authentication support is for Windows only @@ -1871,6 +1892,9 @@ function Add-PodeAuthUserFile { $SuccessUseOrigin ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # ensure the name doesn't already exist if (Test-PodeAuthExists -Name $Name) { # Authentication method already defined: {0} @@ -2033,6 +2057,9 @@ function Add-PodeAuthWindowsLocal { $SuccessUseOrigin ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # ensure we're on Windows! if (!(Test-PodeIsWindows)) { # Windows Local Authentication support is for Windows only diff --git a/src/Public/Caching.ps1 b/src/Public/Caching.ps1 index acddbfde1..684525138 100644 --- a/src/Public/Caching.ps1 +++ b/src/Public/Caching.ps1 @@ -118,6 +118,9 @@ function Set-PodeCache { $Storage = $null ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # use the global settable default here if ($Ttl -le 0) { $Ttl = $PodeContext.Server.Cache.DefaultTtl @@ -227,6 +230,9 @@ function Remove-PodeCache { $Storage = $null ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # inmem or custom storage? if ([string]::IsNullOrEmpty($Storage)) { $Storage = $PodeContext.Server.Cache.DefaultStorage @@ -273,6 +279,9 @@ function Clear-PodeCache { $Storage = $null ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # inmem or custom storage? if ([string]::IsNullOrEmpty($Storage)) { $Storage = $PodeContext.Server.Cache.DefaultStorage @@ -358,6 +367,9 @@ function Add-PodeCacheStorage { $Default ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # test if storage already exists if (Test-PodeCacheStorage -Name $Name) { # Cache Storage with name already exists @@ -402,6 +414,9 @@ function Remove-PodeCacheStorage { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $null = $PodeContext.Server.Cache.Storage.Remove($Name) } @@ -474,6 +489,9 @@ function Set-PodeCacheDefaultStorage { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $PodeContext.Server.Cache.DefaultStorage = $Name } @@ -515,6 +533,9 @@ function Set-PodeCacheDefaultTtl { $Value ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if ($Value -le 0) { return } diff --git a/src/Public/Core.ps1 b/src/Public/Core.ps1 index 51303be3b..3229b5d4d 100644 --- a/src/Public/Core.ps1 +++ b/src/Public/Core.ps1 @@ -131,6 +131,9 @@ function Start-PodeServer { $EnableBreakpoints ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # ensure the session is clean $PodeContext = $null $ShowDoneMessage = $true @@ -176,6 +179,9 @@ function Start-PodeServer { [Console]::TreatControlCAsInput = $true } + if ($PodeContext.Server.Logging.Enabled) { + Enable-PodeLogging + } # start the file monitor for interally restarting Start-PodeFileMonitor @@ -903,6 +909,9 @@ function Add-PodeEndpoint { $Default ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # error if serverless Test-PodeIsServerless -FunctionName 'Add-PodeEndpoint' -ThrowError @@ -1323,6 +1332,10 @@ function Set-PodeDefaultFolder { [string] $Path ) + + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if (Test-Path -Path $Path -PathType Container) { $PodeContext.Server.DefaultFolders[$Type] = $Path } diff --git a/src/Public/EndWare.ps1 b/src/Public/EndWare.ps1 new file mode 100644 index 000000000..f9b5418bb --- /dev/null +++ b/src/Public/EndWare.ps1 @@ -0,0 +1,68 @@ +<# +.SYNOPSIS +Adds a ScriptBlock as Endware to run at the end of each web Request. + +.DESCRIPTION +Adds a ScriptBlock as Endware to run at the end of each web Request. + +.PARAMETER ScriptBlock +The ScriptBlock to add. It will be supplied the current web event. + +.PARAMETER ArgumentList +An array of arguments to supply to the Endware's ScriptBlock. + +.EXAMPLE +Add-PodeEndware -ScriptBlock { /* logic */ } +#> +function Add-PodeEndware { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [scriptblock] + $ScriptBlock, + + [Parameter()] + [object[]] + $ArgumentList + ) + + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + + # check for scoped vars + $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState + + # add the scriptblock to array of endware that needs to be run + $PodeContext.Server.Endware += @{ + Logic = $ScriptBlock + UsingVariables = $usingVars + Arguments = $ArgumentList + } +} + +<# +.SYNOPSIS +Automatically loads endware ps1 files + +.DESCRIPTION +Automatically loads endware ps1 files from either a /endware folder, or a custom folder. Saves space dot-sourcing them all one-by-one. + +.PARAMETER Path +Optional Path to a folder containing ps1 files, can be relative or literal. + +.EXAMPLE +Use-PodeEndware + +.EXAMPLE +Use-PodeEndware -Path './endware' +#> +function Use-PodeEndware { + [CmdletBinding()] + param( + [Parameter()] + [string] + $Path + ) + + Use-PodeFolder -Path $Path -DefaultPath 'endware' +} \ No newline at end of file diff --git a/src/Public/Events.ps1 b/src/Public/Events.ps1 index 9d129667c..56c17581f 100644 --- a/src/Public/Events.ps1 +++ b/src/Public/Events.ps1 @@ -182,6 +182,9 @@ function Clear-PodeEvent { $Type ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $null = $PodeContext.Server.Events[$Type].Clear() } diff --git a/src/Public/FileWatchers.ps1 b/src/Public/FileWatchers.ps1 index 8b4a4aeca..6a03474f8 100644 --- a/src/Public/FileWatchers.ps1 +++ b/src/Public/FileWatchers.ps1 @@ -106,6 +106,9 @@ function Add-PodeFileWatcher { $PassThru ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # set random name if ([string]::IsNullOrEmpty($Name)) { $Name = New-PodeGuid -Secure @@ -143,7 +146,7 @@ function Add-PodeFileWatcher { # test if we have the file watcher already if (Test-PodeFileWatcher -Name $Name) { - # A File Watcher named has already been defined + # A File Watcher named has already been defined throw ($PodeLocale.fileWatcherAlreadyDefinedExceptionMessage -f $Name) } @@ -278,6 +281,9 @@ function Remove-PodeFileWatcher { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $null = $PodeContext.Fim.Items.Remove($Name) } @@ -295,6 +301,9 @@ function Clear-PodeFileWatchers { [CmdletBinding()] param() + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $PodeContext.Fim.Items.Clear() } @@ -323,4 +332,4 @@ function Use-PodeFileWatchers { ) Use-PodeFolder -Path $Path -DefaultPath 'filewatchers' -} +} \ No newline at end of file diff --git a/src/Public/Flash.ps1 b/src/Public/Flash.ps1 index 6f42a4b3c..66db3f3e0 100644 --- a/src/Public/Flash.ps1 +++ b/src/Public/Flash.ps1 @@ -27,6 +27,9 @@ function Add-PodeFlashMessage { $Message ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # if sessions haven't been setup, error if (!(Test-PodeSessionsEnabled)) { # Sessions are required to use Flash messages @@ -60,6 +63,9 @@ function Clear-PodeFlashMessages { [CmdletBinding()] param() + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # if sessions haven't been setup, error if (!(Test-PodeSessionsEnabled)) { # Sessions are required to use Flash messages @@ -166,6 +172,9 @@ function Remove-PodeFlashMessage { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # if sessions haven't been setup, error if (!(Test-PodeSessionsEnabled)) { # Sessions are required to use Flash messages diff --git a/src/Public/Handlers.ps1 b/src/Public/Handlers.ps1 index 59c31ba58..77566b10a 100644 --- a/src/Public/Handlers.ps1 +++ b/src/Public/Handlers.ps1 @@ -54,12 +54,15 @@ function Add-PodeHandler { $ArgumentList ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # error if serverless Test-PodeIsServerless -FunctionName 'Add-PodeHandler' -ThrowError # ensure handler isn't already set if ($PodeContext.Server.Handlers[$Type].ContainsKey($Name)) { - # [Type] Name: Handler already defined + # [Type] Name: Handler already defined throw ($PodeLocale.handlerAlreadyDefinedExceptionMessage -f $Type, $Name) } @@ -109,6 +112,9 @@ function Remove-PodeHandler { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # ensure handler does exist if (!$PodeContext.Server.Handlers[$Type].ContainsKey($Name)) { return @@ -140,6 +146,9 @@ function Clear-PodeHandlers { $Type ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if (![string]::IsNullOrWhiteSpace($Type)) { $PodeContext.Server.Handlers[$Type].Clear() } diff --git a/src/Public/Headers.ps1 b/src/Public/Headers.ps1 index 84a907b2f..660d20026 100644 --- a/src/Public/Headers.ps1 +++ b/src/Public/Headers.ps1 @@ -87,6 +87,9 @@ function Add-PodeHeaderBulk { $Strict ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + foreach ($key in $Values.Keys) { $value = $Values[$key] diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 18a004c7f..33e21de17 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -1,66 +1,141 @@ <# .SYNOPSIS -Create a new method of outputting logs. + Create a new method of outputting logs. .DESCRIPTION -Create a new method of outputting logs. + Create a new method of outputting logs. .PARAMETER Terminal -If supplied, will use the inbuilt Terminal logging output method. + If supplied, will use the inbuilt Terminal logging output method. .PARAMETER File -If supplied, will use the inbuilt File logging output method. + If supplied, will use the inbuilt File logging output method. .PARAMETER Path -The File Path of where to store the logs. + The File Path of where to store the logs. .PARAMETER Name -The File Name to prepend new log files using. + The File Name to prepend new log files using. + +.PARAMETER Format + The format of the log entries for the File logging method. Options are: RFC3164, RFC5424, Simple, Default (Default: Default). + +.PARAMETER Separator + The separator to use in log entries for the File logging method (Default: ' '). + +.PARAMETER MaxLength + The maximum length of log entries for the File logging method (Default: -1). + +.PARAMETER MaxDays + The maximum number of days to keep logs, before Pode automatically removes them. + +.PARAMETER MaxSize + The maximum size of a log file, before Pode starts writing to a new log file. .PARAMETER EventViewer -If supplied, will use the inbuilt Event Viewer logging output method. + If supplied, will use the inbuilt Event Viewer logging output method. .PARAMETER EventLogName -Optional Log Name for the Event Viewer (Default: Application) + Optional Log Name for the Event Viewer (Default: Application) .PARAMETER Source -Optional Source for the Event Viewer (Default: Pode) + Optional Source for the Event Viewer (Default: Pode) .PARAMETER EventID -Optional EventID for the Event Viewer (Default: 0) + Optional EventID for the Event Viewer (Default: 0) .PARAMETER Batch -An optional batch size to write log items in bulk (Default: 1) + An optional batch size to write log items in bulk (Default: 1) .PARAMETER BatchTimeout -An optional batch timeout, in seconds, to send items off for writing if a log item isn't received (Default: 0) + An optional batch timeout, in seconds, to send items off for writing if a log item isn't received (Default: 0) -.PARAMETER MaxDays -The maximum number of days to keep logs, before Pode automatically removes them. +.PARAMETER Custom + If supplied, will allow you to create a Custom Logging output method. -.PARAMETER MaxSize -The maximum size of a log file, before Pode starts writing to a new log file. +.PARAMETER UseRunspace + If supplied, the Custom Logging output method will use its own separated runspace -.PARAMETER Custom -If supplied, will allow you to create a Custom Logging output method. +.PARAMETER CustomOptions + An hastable of properties to supply to the Custom Logging output method's ScriptBlock when used inside a runspace. + The content is available using the variable `$options` .PARAMETER ScriptBlock -The ScriptBlock that defines how to output a log item. + The ScriptBlock that defines how to output a log item. .PARAMETER ArgumentList -An array of arguments to supply to the Custom Logging output method's ScriptBlock. + An array of arguments to supply to the Custom Logging output method's ScriptBlock. + +.PARAMETER Syslog + If supplied, will use the Syslog logging output method. + +.PARAMETER Server + The Syslog server to send logs to. + +.PARAMETER Port + The port on the Syslog server (Default: 514). + +.PARAMETER Transport + The transport protocol to use (Default: UDP). + +.PARAMETER TlsProtocol + The TLS protocol version to use (Default: TLS 1.3). + +.PARAMETER SyslogProtocol + The Syslog protocol to use (Default: RFC5424). + +.PARAMETER Encoding + The encoding to use for the Syslog messages (Default: UTF8). + +.PARAMETER SkipCertificateCheck + Skip certificate validation for TLS connections. + +.PARAMETER Restful + If supplied, will use the Restful logging output method. + +.PARAMETER BaseUrl + The base URL for the Restful logging endpoint. + +.PARAMETER Platform + The platform for Restful logging (Splunk, LogInsight). + +.PARAMETER Token + The token for authentication with Restful servers that require it. + +.PARAMETER Id + The LogInsight collector ID. + +.PARAMETER FailureAction + Defines the behavior in case of failure. Options are: Ignore, Report, Halt (Default: Ignore). + +.PARAMETER DataFormat + The date format to use for the log entries (Default: 'dd/MMM/yyyy:HH:mm:ss zzz'). + +.PARAMETER ISO8601 + If set, the date format will be ISO 8601 compliant (equivalent to -DataFormat 'yyyy-MM-ddTHH:mm:ssK') + This parameter is mutually exclusive with DataFormat. + +.PARAMETER AsUTC + If set, the time will be logged in UTC instead of local time. + +.EXAMPLE + $term_logging = New-PodeLoggingMethod -Terminal + +.EXAMPLE + $file_logging = New-PodeLoggingMethod -File -Path ./logs -Name 'requests' .EXAMPLE -$term_logging = New-PodeLoggingMethod -Terminal + $custom_logging = New-PodeLoggingMethod -Custom -ScriptBlock { /* logic */ } .EXAMPLE -$file_logging = New-PodeLoggingMethod -File -Path ./logs -Name 'requests' + $syslog_logging = New-PodeLoggingMethod -Syslog -Server '192.168.1.1' -Port 514 -Transport 'UDP' .EXAMPLE -$custom_logging = New-PodeLoggingMethod -Custom -ScriptBlock { /* logic */ } + $restful_logging = New-PodeLoggingMethod -Restful -BaseUrl 'https://logserver.example.com' -Platform 'Splunk' -Token 'your-token' #> function New-PodeLoggingMethod { [CmdletBinding(DefaultParameterSetName = 'Terminal')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUSeDeclaredVarsMoreThanAssignments', '')] [OutputType([hashtable])] param( [Parameter(ParameterSetName = 'Terminal')] @@ -79,6 +154,19 @@ function New-PodeLoggingMethod { [string] $Name, + [Parameter( ParameterSetName = 'File')] + [ValidateSet('RFC3164' , 'RFC5424', 'Simple', 'Default' )] + [string] + $Format = 'Default', + + [Parameter( ParameterSetName = 'File')] + [string] + $Separator = ' ', + + [Parameter( ParameterSetName = 'File')] + [int] + $MaxLength = -1, + [Parameter(ParameterSetName = 'EventViewer')] [switch] $EventViewer, @@ -88,6 +176,8 @@ function New-PodeLoggingMethod { $EventLogName = 'Application', [Parameter(ParameterSetName = 'EventViewer')] + [Parameter(ParameterSetName = 'Syslog')] + [Parameter(ParameterSetName = 'File')] [string] $Source = 'Pode', @@ -127,11 +217,21 @@ function New-PodeLoggingMethod { [int] $MaxSize = 0, - [Parameter(ParameterSetName = 'Custom')] + [Parameter(Mandatory = $true, ParameterSetName = 'Custom')] + [Parameter(Mandatory = $true, ParameterSetName = 'CustomRunspace')] [switch] $Custom, + [Parameter(Mandatory = $true, ParameterSetName = 'CustomRunspace')] + [switch] + $UseRunspace, + + [Parameter(ParameterSetName = 'CustomRunspace')] + [hashtable] + $CustomOptions = @{}, + [Parameter(Mandatory = $true, ParameterSetName = 'Custom')] + [Parameter(Mandatory = $true, ParameterSetName = 'CustomRunspace')] [ValidateScript({ if (Test-PodeIsEmpty $_) { # A non-empty ScriptBlock is required for the Custom logging output method @@ -145,11 +245,119 @@ function New-PodeLoggingMethod { [Parameter(ParameterSetName = 'Custom')] [object[]] - $ArgumentList + $ArgumentList, + + [Parameter(Mandatory = $true, ParameterSetName = 'Syslog')] + [switch] + $Syslog, + + [Parameter(Mandatory = $true, ParameterSetName = 'Syslog')] + [string] + $Server, + + [Parameter( ParameterSetName = 'Syslog')] + [Int16] + $Port = 514, + + [Parameter( ParameterSetName = 'Syslog')] + [ValidateSet('UDP', 'TCP', 'TLS' )] + [string] + $Transport = 'UDP', + + [Parameter( ParameterSetName = 'Syslog')] + [System.Security.Authentication.SslProtocols] + $TlsProtocol = [System.Security.Authentication.SslProtocols]::Tls13, + + [Parameter( ParameterSetName = 'Syslog')] + [ValidateSet('RFC3164' , 'RFC5424')] + [string] + $SyslogProtocol = 'RFC5424', + + [Parameter( ParameterSetName = 'Syslog')] + [Parameter( ParameterSetName = 'File')] + [ValidateSet('ASCII', 'BigEndianUnicode', 'Default', 'Unicode', 'UTF32', 'UTF7', 'UTF8')] + [string] + $Encoding = 'UTF8', + + [Parameter( ParameterSetName = 'Syslog')] + [Parameter(Mandatory = $true, ParameterSetName = 'Restful')] + [switch] + $SkipCertificateCheck, + + [Parameter(Mandatory = $true, ParameterSetName = 'Restful')] + [switch] + $Restful, + + [Parameter(Mandatory = $true, ParameterSetName = 'Restful')] + [ValidatePattern('^(https?://|/).+')] + [string] + $BaseUrl, + + [Parameter( ParameterSetName = 'Restful')] + [ValidateSet( 'Splunk', 'LogInsight')] + $Platform = 'Splunk', + + [Parameter( ParameterSetName = 'Restful')] + [string] + $Token, + + [Parameter( ParameterSetName = 'Restful')] + [string] + $Id, + + [Parameter(ParameterSetName = 'EventViewer')] + [Parameter(ParameterSetName = 'File')] + [Parameter(ParameterSetName = 'CustomRunspace')] + [Parameter( ParameterSetName = 'Restful')] + [Parameter( ParameterSetName = 'Syslog')] + [string] + [ValidateSet('Ignore', 'Report', 'Halt' )] + $FailureAction = 'Ignore', + + [Parameter()] + [ValidateScript({ + # Define a sample date to test the format + $sampleDate = [DateTime]::Now + try { + # Try to format the sample date using the provided format + $formattedDate = $sampleDate.ToString($_) + + # Try to parse the formatted date back to a DateTime object using the same format + [DateTime]::ParseExact($formattedDate, $_, $null) + + # If no exceptions are thrown, the format is valid + $true + } + catch { + # If an exception is thrown, the format is invalid + $false + } + })] + [string] + $DataFormat, + + [Parameter()] + [switch] + $ISO8601, + + [Parameter()] + [switch] + $AsUTC ) + if ((! [string]::IsNullOrEmpty($DataFormat)) -and $ISO8601.IsPresent) { + throw ("Parameters '{0}' and '{1}' are mutually exclusive." -f 'DataFormat', 'ISO8601') + } + if ($ISO8601.IsPresent) { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' + } + else { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + # batch details $batchInfo = @{ + Id = New-PodeGuid Size = $Batch Timeout = $BatchTimeout LastUpdate = $null @@ -160,22 +368,36 @@ function New-PodeLoggingMethod { # return info on appropriate logging type switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'terminal' { - return @{ + $methodId = New-PodeGuid + $PodeContext.Server.Logging.Method[$methodId] = @{ ScriptBlock = (Get-PodeLoggingTerminalMethod) - Batch = $batchInfo - Arguments = @{} + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + } + return @{ + Id = $methodId + Batch = $batchInfo + Logger = @() + Arguments = @{ + DataFormat = $DataFormat + AsUTC = $AsUTC + } } } 'file' { $Path = (Protect-PodeValue -Value $Path -Default './logs') - $Path = (Get-PodeRelativePath -Path $Path -JoinRoot) + $Path = (Get-PodeRelativePath -Path $Path -JoinRoot -Resolve) $null = New-Item -Path $Path -ItemType Directory -Force - - return @{ + $methodId = New-PodeGuid + $PodeContext.Server.Logging.Method[$methodId] = @{ ScriptBlock = (Get-PodeLoggingFileMethod) - Batch = $batchInfo - Arguments = @{ + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + } + return @{ + Id = $methodId + Batch = $batchInfo + Logger = @() + Arguments = @{ Name = $Name Path = $Path MaxDays = $MaxDays @@ -183,6 +405,14 @@ function New-PodeLoggingMethod { FileId = 0 Date = $null NextClearDown = [datetime]::Now.Date + FailureAction = $FailureAction + DataFormat = $DataFormat + AsUTC = $AsUTC + Encoding = $Encoding + Format = $Format + MaxLength = $MaxLength + Source = $Source + Separator = $Separator } } } @@ -199,26 +429,147 @@ function New-PodeLoggingMethod { $null = [System.Diagnostics.EventLog]::CreateEventSource($Source, $EventLogName) } - return @{ + $methodId = New-PodeGuid + $PodeContext.Server.Logging.Method[$methodId] = @{ ScriptBlock = (Get-PodeLoggingEventViewerMethod) - Batch = $batchInfo - Arguments = @{ - LogName = $EventLogName - Source = $Source - ID = $EventID + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + } + return @{ + Id = $methodId + Batch = $batchInfo + Logger = @() + Arguments = @{ + LogName = $EventLogName + Source = $Source + ID = $EventID + FailureAction = $FailureAction + DataFormat = $DataFormat + AsUTC = $AsUTC } } } - 'custom' { - $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState + 'syslog' { + # Get the encoding object based on the selected encoding name + $selectedEncoding = [System.Text.Encoding]::$Encoding + if ($null -eq $selectedEncoding) { + throw ($PodeLocale.invalidEncodingExceptionMessage -f $Encoding) + } + + $methodId = New-PodeGuid + $PodeContext.Server.Logging.Method[$methodId] = @{ + ScriptBlock = (Get-PodeLoggingSysLogMethod) + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + } return @{ - ScriptBlock = $ScriptBlock - UsingVariables = $usingVars - Batch = $batchInfo - Arguments = $ArgumentList + Id = $methodId + Batch = $batchInfo + Logger = @() + Arguments = @{ + Server = $Server + Port = $Port + Transport = $Transport + Hostname = $Hostname + Source = $Source + TslProtocols = $TlsProtocol + SkipCertificateCheck = $SkipCertificateCheck + SyslogProtocol = $SyslogProtocol + Encoding = $selectedEncoding + FailureAction = $FailureAction + DataFormat = $DataFormat + AsUTC = $AsUTC + } + } + } + + 'restful' { + $methodId = New-PodeGuid + $PodeContext.Server.Logging.Method[$methodId] = @{ + ScriptBlock = (Get-PodeLoggingRestfulMethod) + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() } + return @{ + Id = $methodId + Batch = $batchInfo + Logger = @() + Arguments = @{ + BaseUrl = $BaseUrl + Platform = $Platform + Hostname = $Hostname + Source = $Source + SkipCertificateCheck = $SkipCertificateCheck + Token = $Token + Id = $Id + FailureAction = $FailureAction + DataFormat = $DataFormat + AsUTC = $AsUTC + } + } + } + + { ($_ -eq 'CustomRunspace') -or ($_ -eq 'custom') } { + $methodId = New-PodeGuid + if ($UseRunspace.IsPresent) { + $enanchedScriptBlock = { + param($MethodId) + + ([System.Management.Automation.Runspaces.Runspace]::DefaultRunspace).Name = "LoggingCustomMethod_$MethodId" + + $log = @{} + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + Start-Sleep -Milliseconds 100 + + if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { + if ($null -ne $log) { + $Item = $log.item + $Options = $log.options + $RawItem = $log.rawItem + try { + # Original ScriptBlock Start + <# ScriptBlock #> + # Original ScriptBlock End + } + catch { + Invoke-PodeHandleFailure -Message "Custom Logging $MethodId Error. message: $_" -FailureAction $options.FailureAction + } + } + } + } + } + + $PodeContext.Server.Logging.Method[$methodId] = @{ + ScriptBlock = [ScriptBlock]::Create( $enanchedScriptBlock.ToString().Replace('<# ScriptBlock #>', $ScriptBlock.ToString())) + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + } + return @{ + Id = $methodId + Batch = $batchInfo + Logger = @() + Arguments = @{ + FailureAction = $FailureAction + DataFormat = $DataFormat + AsUTC = $AsUTC + } + $CustomOptions + } + } + else { + $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState + + return @{ + Id = $methodId + ScriptBlock = $ScriptBlock + UsingVariables = $usingVars + Batch = $batchInfo + Logger = @() + Arguments = $ArgumentList + FailureAction = $FailureAction + DataFormat = $DataFormat + AsUTC = $AsUTC + NoRunspace = $true + } + } + } } } @@ -226,28 +577,31 @@ function New-PodeLoggingMethod { <# .SYNOPSIS -Enables Request Logging using a supplied output method. + Enables Request Logging using a supplied output method. .DESCRIPTION -Enables Request Logging using a supplied output method. + Enables Request Logging using a supplied output method. .PARAMETER Method -The Method to use for output the log entry (From New-PodeLoggingMethod). + The Method to use for output the log entry (From New-PodeLoggingMethod). .PARAMETER UsernameProperty -An optional property path within the $WebEvent.Auth.User object for the user's Username. (Default: Username). + An optional property path within the $WebEvent.Auth.User object for the user's Username. (Default: Username). .PARAMETER Raw -If supplied, the log item returned will be the raw Request item as a hashtable and not a string (for Custom methods). + If supplied, the log item returned will be the raw Request item as a hashtable and not a string. + +.PARAMETER LogFormat + The format to use for the log entries. Options are: Extended, Common, Combined, JSON (Default: Combined). .EXAMPLE -New-PodeLoggingMethod -Terminal | Enable-PodeRequestLogging + New-PodeLoggingMethod -Terminal | Enable-PodeRequestLogging #> function Enable-PodeRequestLogging { [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [hashtable] + [hashtable[]] $Method, [Parameter()] @@ -255,52 +609,72 @@ function Enable-PodeRequestLogging { $UsernameProperty, [switch] - $Raw + $Raw, + + [string] + [ValidateSet('Extended', 'Common', 'Combined', 'JSON' )] + $LogFormat = 'Combined' ) + begin { + $pipelineMethods = @() - Test-PodeIsServerless -FunctionName 'Enable-PodeRequestLogging' -ThrowError + Test-PodeIsServerless -FunctionName 'Enable-PodeRequestLogging' -ThrowError - $name = Get-PodeRequestLoggingName + $name = Get-PodeRequestLoggingName - # error if it's already enabled - if ($PodeContext.Server.Logging.Types.Contains($name)) { - # Request Logging has already been enabled - throw ($PodeLocale.requestLoggingAlreadyEnabledExceptionMessage) - } + # error if it's already enabled + if ($PodeContext.Server.Logging.Type.Contains($name)) { + # Request Logging has already been enabled + throw ($PodeLocale.loggingAlreadyEnabledExceptionMessage -f 'Request') + } - # ensure the Method contains a scriptblock - if (Test-PodeIsEmpty $Method.ScriptBlock) { - # The supplied output Method for Request Logging requires a valid ScriptBlock - throw ($PodeLocale.loggingMethodRequiresValidScriptBlockExceptionMessage -f 'Request') + # username property + if ([string]::IsNullOrWhiteSpace($UsernameProperty)) { + $UsernameProperty = 'Username' + } } - - # username property - if ([string]::IsNullOrWhiteSpace($UsernameProperty)) { - $UsernameProperty = 'Username' + process { + # ensure the Method contains a scriptblock + if ((! $PodeContext.Server.Logging.Method.ContainsKey($_.Id)) -and (! $_.ContainsKey('Scriptblock'))) { + # The supplied output Method for Request Logging requires a valid ScriptBlock + throw ($PodeLocale.loggingMethodRequiresValidScriptBlockExceptionMessage -f 'Request') + } + $pipelineMethods += $_ } + end { - # add the request logger - $PodeContext.Server.Logging.Types[$name] = @{ - Method = $Method - ScriptBlock = (Get-PodeLoggingInbuiltType -Type Requests) - Properties = @{ - Username = $UsernameProperty + if ($pipelineMethods.Count -gt 1) { + $Method = $pipelineMethods } - Arguments = @{ - Raw = $Raw + + # add the request logger + $PodeContext.Server.Logging.Type[$name] = @{ + Method = $Method + ScriptBlock = (Get-PodeLoggingInbuiltType -Type Requests) + Properties = @{ + Username = $UsernameProperty + } + Arguments = @{ + Raw = $Raw + DataFormat = $Method.Arguments.DataFormat + LogFormat = $LogFormat + } + Standard = $true } + + $Method.ForEach({ $_.Logger += $name }) } } <# .SYNOPSIS -Disables Request Logging. + Disables Request Logging. .DESCRIPTION -Disables Request Logging. + Disables Request Logging. .EXAMPLE -Disable-PodeRequestLogging + Disable-PodeRequestLogging #> function Disable-PodeRequestLogging { [CmdletBinding()] @@ -311,28 +685,28 @@ function Disable-PodeRequestLogging { <# .SYNOPSIS -Enables Error Logging using a supplied output method. + Enables Error Logging using a supplied output method. .DESCRIPTION -Enables Error Logging using a supplied output method. + Enables Error Logging using a supplied output method. .PARAMETER Method -The Method to use for output the log entry (From New-PodeLoggingMethod). + The Method to use for output the log entry (From New-PodeLoggingMethod). .PARAMETER Levels -The Levels of errors that should be logged (default is Error). + The Levels of errors that should be logged (default is Error). .PARAMETER Raw -If supplied, the log item returned will be the raw Error item as a hashtable and not a string (for Custom methods). + If supplied, the log item returned will be the raw Error item as a hashtable and not a string (for Custom methods). .EXAMPLE -New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging + New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging #> function Enable-PodeErrorLogging { [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [hashtable] + [hashtable[]] $Method, [Parameter()] @@ -345,36 +719,238 @@ function Enable-PodeErrorLogging { $Raw ) - $name = Get-PodeErrorLoggingName + begin { + $pipelineMethods = @() - # error if it's already enabled - if ($PodeContext.Server.Logging.Types.Contains($name)) { - # Error Logging has already been enabled - throw ($PodeLocale.errorLoggingAlreadyEnabledExceptionMessage) + $name = Get-PodeErrorLoggingName + # error if it's already enabled + if ($PodeContext.Server.Logging.Type.Contains($Name)) { + # Error Logging has already been enabled + throw ($PodeLocale.loggingAlreadyEnabledExceptionMessage -f 'Error') + } + # all errors? + if ($Levels -contains '*') { + $Levels = @('Error', 'Warning', 'Informational', 'Verbose', 'Debug') + } } - # ensure the Method contains a scriptblock - if (Test-PodeIsEmpty $Method.ScriptBlock) { - # The supplied output Method for Error Logging requires a valid ScriptBlock - throw ($PodeLocale.loggingMethodRequiresValidScriptBlockExceptionMessage -f 'Error') + process { + # ensure the Method contains a scriptblock + if ((! $PodeContext.Server.Logging.Method.ContainsKey($_.Id)) -and (! $_.ContainsKey('Scriptblock'))) { + # The supplied output Method for Error Logging requires a valid ScriptBlock + throw ($PodeLocale.loggingMethodRequiresValidScriptBlockExceptionMessage -f 'Error') + } + $pipelineMethods += $_ } - # all errors? - if ($Levels -contains '*') { - $Levels = @('Error', 'Warning', 'Informational', 'Verbose', 'Debug') + end { + + if ($pipelineMethods.Count -gt 1) { + $Method = $pipelineMethods + } + + # add the error logger + $PodeContext.Server.Logging.Type[$name] = @{ + Method = $Method + ScriptBlock = (Get-PodeLoggingInbuiltType -Type Errors) + Arguments = @{ + Raw = $Raw + Levels = $Levels + DataFormat = $Method.Arguments.DataFormat + } + Standard = $true + } + + $Method.ForEach({ $_.Logger += $name }) } +} - # add the error logger - $PodeContext.Server.Logging.Types[$name] = @{ - Method = $Method - ScriptBlock = (Get-PodeLoggingInbuiltType -Type Errors) - Arguments = @{ - Raw = $Raw - Levels = $Levels +<# +.SYNOPSIS + Enables a generic logging method in Pode. + +.DESCRIPTION + This function enables a generic logging method in Pode, allowing logs to be written based on the defined method and log levels. It ensures the method is not already enabled and validates the provided script block. + +.PARAMETER Method + The hashtable defining the logging method, including the ScriptBlock for log output. + +.PARAMETER Levels + An array of log levels to be enabled for the logging method (Default: 'Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Info', 'Verbose', 'Debug'). + +.PARAMETER Name + The name of the logging method to be enabled. + +.PARAMETER Raw + If set, the raw log data will be included in the logging output. + +.EXAMPLE + $method = New-PodeLoggingMethod -syslog -Server 127.0.0.1 -Transport UDP + $method | Enable-PodeGeneralLogging -Name "mysyslog" +#> +function Enable-PodeGeneralLogging { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [hashtable[]] + $Method, + + [string[]] + $Levels = @('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Info', 'Verbose', 'Debug'), + + [Parameter(Mandatory = $true)] + [string] + $Name, + + [switch] + $Raw + ) + begin { + $pipelineMethods = @() + # error if it's already enabled + if ($PodeContext.Server.Logging.Type.Contains($Name)) { + throw ($PodeLocale.loggingAlreadyEnabledExceptionMessage -f $Name) + } + + } + + process { + # ensure the Method contains a scriptblock + if ((! $PodeContext.Server.Logging.Method.ContainsKey($_.Id)) -and (! $_.ContainsKey('Scriptblock'))) { + # The supplied output Method for the '{0}' Logging method requires a valid ScriptBlock. + throw ($PodeLocale.loggingMethodRequiresValidScriptBlockExceptionMessage -f $Name) + } + $pipelineMethods += $_ + } + end { + + if ($pipelineMethods.Count -gt 1) { + $Method = $pipelineMethods + } + + # add the error logger + $PodeContext.Server.Logging.Type[$Name] = @{ + Method = $Method + ScriptBlock = (Get-PodeLoggingInbuiltType -Type General) + Arguments = @{ + Raw = $Raw + Levels = $Levels + DataFormat = $Method.Arguments.DataFormat + } + Standard = $true } + + $Method.ForEach({ $_.Logger += $Name }) } } + +<# +.SYNOPSIS + Disables a generic logging method in Pode. + +.DESCRIPTION + This function disables a generic logging method in Pode. + +.PARAMETER Name + The name of the logging method to be disable. + +.EXAMPLE + Disable-PodeGeneralLogging -Name 'TestLog' +#> +function Disable-PodeGeneralLogging { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] + $Name) + + Remove-PodeLogger -Name $Name +} + + +<# +.SYNOPSIS + Enables the trace logging in Pode. + +.DESCRIPTION + This function enables the trace logging in Pode, allowing logs to be written based on the defined method and log levels. It ensures the method is not already enabled and validates the provided script block. + +.PARAMETER Method + The hashtable defining the logging method, including the ScriptBlock for log output. + +.PARAMETER Raw + If set, the raw log data will be included in the logging output. + +.EXAMPLE + $method = New-PodeLoggingMethod -syslog -Server 127.0.0.1 -Transport UDP + $method | Enable-PodeTraceLogging +#> +function Enable-PodeTraceLogging { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [hashtable[]] + $Method, + + [switch] + $Raw + ) + begin { + $pipelineMethods = @() + $name = Get-PodeMainLoggingName + # error if it's already enabled + if ($PodeContext.Server.Logging.Type.Contains($name)) { + throw ($PodeLocale.loggingAlreadyEnabledExceptionMessage -f $name) + } + } + + process { + # ensure the Method contains a scriptblock + if ((! $PodeContext.Server.Logging.Method.ContainsKey($_.Id)) -and (! $_.ContainsKey('Scriptblock'))) { + # The supplied output Method for the '{0}' Logging method requires a valid ScriptBlock. + throw ($PodeLocale.loggingMethodRequiresValidScriptBlockExceptionMessage -f 'Main') + } + $pipelineMethods += $_ + } + + end { + + if ($pipelineMethods.Count -gt 1) { + $Method = $pipelineMethods + } + + # add the error logger + $PodeContext.Server.Logging.Type[$name] = @{ + Method = $Method + ScriptBlock = (Get-PodeLoggingInbuiltType -Type Main) + Arguments = @{ + Raw = $Raw + DataFormat = $Method.Arguments.DataFormat + } + Standard = $true + } + $Method.ForEach({ $_.Logger += $name }) + } +} + +<# +.SYNOPSIS +Disables the trace logging method in Pode. + +.DESCRIPTION +This function disables the trace logging method in Pode. + +.EXAMPLE +Disable-PodeTraceLogging +#> +function Disable-PodeTraceLogging { + [CmdletBinding()] + param() + + Remove-PodeLogger -Name (Get-PodeMainLoggingName) +} + <# .SYNOPSIS Disables Error Logging. @@ -390,29 +966,30 @@ function Disable-PodeErrorLogging { param() Remove-PodeLogger -Name (Get-PodeErrorLoggingName) + } <# .SYNOPSIS -Adds a custom Logging method for parsing custom log items. + Adds a custom Logging method for parsing custom log items. .DESCRIPTION -Adds a custom Logging method for parsing custom log items. + Adds a custom Logging method for parsing custom log items. .PARAMETER Name -A unique Name for the Logging method. + A unique Name for the Logging method. .PARAMETER Method -The Method to use for output the log entry (From New-PodeLoggingMethod). + The Method to use for output the log entry (From New-PodeLoggingMethod). .PARAMETER ScriptBlock -The ScriptBlock defining logic that transforms an item, and returns it for outputting. + The ScriptBlock defining logic that transforms an item, and returns it for outputting. .PARAMETER ArgumentList -An array of arguments to supply to the Custom Logger's ScriptBlock. + An array of arguments to supply to the Custom Logger's ScriptBlock. .EXAMPLE -New-PodeLoggingMethod -Terminal | Add-PodeLogger -Name 'Main' -ScriptBlock { /* logic */ } + New-PodeLoggingMethod -Terminal | Add-PodeLogger -Name 'Main' -ScriptBlock { /* logic */ } #> function Add-PodeLogger { [CmdletBinding()] @@ -442,42 +1019,60 @@ function Add-PodeLogger { $ArgumentList ) - # ensure the name doesn't already exist - if ($PodeContext.Server.Logging.Types.ContainsKey($Name)) { - # Logging method already defined - throw ($PodeLocale.loggingMethodAlreadyDefinedExceptionMessage -f $Name) + Begin { + $pipelineItemCount = 0 } - # ensure the Method contains a scriptblock - if (Test-PodeIsEmpty $Method.ScriptBlock) { - # The supplied output Method for the Logging method requires a valid ScriptBlock - throw ($PodeLocale.loggingMethodRequiresValidScriptBlockExceptionMessage -f $Name) + Process { + $pipelineItemCount++ } - # check for scoped vars - $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState + End { + if ($pipelineItemCount -gt 1) { + throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name)) + } + + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # add logging method to server - $PodeContext.Server.Logging.Types[$Name] = @{ - Method = $Method - ScriptBlock = $ScriptBlock - UsingVariables = $usingVars - Arguments = $ArgumentList + # ensure the name doesn't already exist + if ($PodeContext.Server.Logging.Type.ContainsKey($Name)) { + # Logging method already defined + throw ($PodeLocale.loggingMethodAlreadyDefinedExceptionMessage -f $Name) + } + + # ensure the Method contains a scriptblock + if (Test-PodeIsEmpty $Method.ScriptBlock) { + # The supplied output Method for the Logging method requires a valid ScriptBlock + throw ($PodeLocale.loggingMethodRequiresValidScriptBlockExceptionMessage -f $Name) + } + + # check for scoped vars + $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState + + # add logging method to server + $PodeContext.Server.Logging.Type[$Name] = @{ + Method = $Method + ScriptBlock = $ScriptBlock + UsingVariables = $usingVars + Arguments = $ArgumentList + } } } <# .SYNOPSIS -Removes a configured Logging method. + Removes a configured Logging method. .DESCRIPTION -Removes a configured Logging method. + Removes a configured Logging method by its name. + This function handles the removal of the logging method and ensures that any associated runspaces and script blocks are properly disposed of if they are no longer in use. .PARAMETER Name -The Name of the Logging method. + The Name of the Logging method. .EXAMPLE -Remove-PodeLogger -Name 'LogName' + Remove-PodeLogger -Name 'LogName' #> function Remove-PodeLogger { [CmdletBinding()] @@ -486,8 +1081,46 @@ function Remove-PodeLogger { [string] $Name ) + Process { + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + + # Check if the specified logging type exists + if ($PodeContext.Server.Logging.Type.Contains($Name)) { + # Retrieve the method associated with the logging type + $method = $PodeContext.Server.Logging.Type[$Name].Method + # If it's not a legacy method remove the runspace + if (! $method.NoRunspace) { + # Remove the logger name from the method's logger collection + if ($method.Logger.Count -eq 1) { + $method.Logger = @() + } + else { + $method.Logger = $method.Logger | Where-Object { $_ -ne $Name } + } + + # Check if there are no more loggers associated with the method + if ($method.Logger.Count -eq 0) { + # If the method's runspace is still active, stop and dispose of it + if ($PodeContext.Server.Logging.Method.ContainsKey($method.Id)) { + $PodeContext.Server.Logging.Method[$method.Id].Runspace.Pipeline.Stop() + $PodeContext.Server.Logging.Method[$method.Id].Runspace.Pipeline.Dispose() + + # Decrease the maximum runspaces for the 'logs' pool if applicable + $maxRunspaces = $PodeContext.RunspacePools['logs'].Pool.GetMaxRunspaces + if ($maxRunspaces -gt 1) { + $PodeContext.RunspacePools['logs'].Pool.SetMaxRunspaces($maxRunspaces - 1) + } + # Remove the method's script block if it exists + $PodeContext.Server.Logging.Method.Remove($method.Id) + } + } + } - $null = $PodeContext.Server.Logging.Types.Remove($Name) + # Finally, remove the logging type from the Types collection + $null = $PodeContext.Server.Logging.Type.Remove($Name) + } + } } <# @@ -498,39 +1131,56 @@ Clears all Logging methods that have been configured. Clears all Logging methods that have been configured. .EXAMPLE -Clear-PodeLoggers +Clear-PodeLogger #> -function Clear-PodeLoggers { +function Clear-PodeLogger { [CmdletBinding()] param() - $PodeContext.Server.Logging.Types.Clear() + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + + $PodeContext.Server.Logging.Type.Clear() +} + +# Create the alias for back compatibility +if (!(Test-Path Alias:Clear-PodeLoggers)) { + New-Alias Clear-PodeLoggers -Value Clear-PodeLogger } <# .SYNOPSIS -Writes and Exception or ErrorRecord using the inbuilt error logging. + Writes an Exception or ErrorRecord using the built-in error logging. .DESCRIPTION -Writes and Exception or ErrorRecord using the inbuilt error logging. + This function logs an Exception or ErrorRecord using Pode's built-in error logging mechanism. It allows specifying the error level and optionally checks for inner exceptions. .PARAMETER Exception -An Exception to write. + An Exception to log. .PARAMETER ErrorRecord -An ErrorRecord to write. + An ErrorRecord to log. + +.PARAMETER ErrorMessage + A custom error message to log. + +.PARAMETER Category + The error category for the custom error message (Default: NotSpecified). .PARAMETER Level -The Level of the error being logged. + The level of the error being logged. Options are: Error, Warning, Informational, Verbose, Debug (Default: Error). .PARAMETER CheckInnerException -If supplied, any exceptions are check for inner exceptions. If one is present, this is also logged. + If specified, any inner exceptions of the provided exception are also logged. + +.PARAMETER ThreadId + The ID of the thread where the error occurred. .EXAMPLE -try { /* logic */ } catch { $_ | Write-PodeErrorLog } + try { /* logic */ } catch { $_ | Write-PodeErrorLog } .EXAMPLE -[System.Exception]::new('error message') | Write-PodeErrorLog + [System.Exception]::new('error message') | Write-PodeErrorLog #> function Write-PodeErrorLog { [CmdletBinding()] @@ -539,10 +1189,18 @@ function Write-PodeErrorLog { [System.Exception] $Exception, - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Error')] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ErrorRecord')] [System.Management.Automation.ErrorRecord] $ErrorRecord, + [Parameter(Mandatory = $true, ParameterSetName = 'ErrorMessage')] + [string] + $ErrorMessage, + + [Parameter( ParameterSetName = 'ErrorMessage')] + [System.Management.Automation.ErrorCategory] + $Category = [System.Management.Automation.ErrorCategory]::NotSpecified, + [Parameter()] [ValidateNotNullOrEmpty()] [ValidateSet('Error', 'Warning', 'Informational', 'Verbose', 'Debug')] @@ -551,111 +1209,208 @@ function Write-PodeErrorLog { [Parameter(ParameterSetName = 'Exception')] [switch] - $CheckInnerException + $CheckInnerException, + + [Parameter()] + [int] + $ThreadId ) - # do nothing if logging is disabled, or error logging isn't setup - $name = Get-PodeErrorLoggingName - if (!(Test-PodeLoggerEnabled -Name $name)) { - return - } + Process { + # do nothing if logging is disabled, or error logging isn't setup + $name = Get-PodeErrorLoggingName + if (!(Test-PodeLoggerEnabled -Name $name)) { + return + } - # do nothing if the error level isn't present - $levels = @(Get-PodeErrorLoggingLevel) - if ($levels -inotcontains $Level) { - return - } + # do nothing if the error level isn't present + $levels = @(Get-PodeErrorLoggingLevel) + if ($levels -inotcontains $Level) { + return + } - # build error object for what we need - switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { - 'exception' { - $item = @{ - Category = $Exception.Source - Message = $Exception.Message - StackTrace = $Exception.StackTrace + # build error object for what we need + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'exception' { + $item = @{ + Category = $Exception.Source + Message = $Exception.Message + StackTrace = $Exception.StackTrace + } } - } - 'error' { - $item = @{ - Category = $ErrorRecord.CategoryInfo.ToString() - Message = $ErrorRecord.Exception.Message - StackTrace = $ErrorRecord.ScriptStackTrace + 'ErrorRecord' { + $item = @{ + Category = $ErrorRecord.CategoryInfo.ToString() + Message = $ErrorRecord.Exception.Message + StackTrace = $ErrorRecord.ScriptStackTrace + } + } + 'ErrorMessage' { + $item = @{ + Category = $Category.ToString() + Message = $ErrorMessage + } } } - } - # add general info - $item['Server'] = $PodeContext.Server.ComputerName - $item['Level'] = $Level - $item['Date'] = [datetime]::Now - $item['ThreadId'] = [int]$ThreadId + # add general info + $item['Server'] = $PodeContext.Server.ComputerName + $item['Level'] = $Level + if ($PodeContext.Server.Logging.Type[$Name].Method.Arguments.AsUTC) { + $Item.Date = [datetime]::UtcNow + } + else { + $Item.Date = [datetime]::Now + } + + if ($ThreadId) { + $Item['ThreadId'] = $ThreadId + } + else { + $item['ThreadId'] = [System.Threading.Thread]::CurrentThread.ManagedThreadId #[int]$ThreadId + } - # add the item to be processed - $null = $PodeContext.LogsToProcess.Add(@{ + $logItem = @{ Name = $name Item = $item - }) + } - # for exceptions, check the inner exception - if ($CheckInnerException -and ($null -ne $Exception.InnerException) -and ![string]::IsNullOrWhiteSpace($Exception.InnerException.Message)) { - $Exception.InnerException | Write-PodeErrorLog + # add the item to be processed + $null = [Pode.PodeLogger]::Enqueue( $logItem) + + # for exceptions, check the inner exception + if ($CheckInnerException -and ($null -ne $Exception.InnerException) -and ![string]::IsNullOrWhiteSpace($Exception.InnerException.Message)) { + $Exception.InnerException | Write-PodeErrorLog + } } } + <# .SYNOPSIS -Write an object to a configured custom Logging method. + Writes an object to a configured custom or built-in logging method. .DESCRIPTION -Write an object to a configured custom Logging method. + This function writes an object to a configured logging method in Pode. + It supports both custom and built-in logging methods, allowing for structured logging with different log levels and messages. .PARAMETER Name -The Name of the Logging method. + The name of the logging method. .PARAMETER InputObject -The Object to write. + The object to write to the logging method. + +.PARAMETER Level + The log level for the custom logging method (Default: 'Informational'). + +.PARAMETER Message + The log message for the custom logging method. + +.PARAMETER Tag + A string that identifies the source application, service, or process generating the log message. + The tag helps in distinguishing log messages from different sources and makes it easier to filter and analyze logs. + It is typically a short identifier such as the application name or process ID. + +.PARAMETER ThreadId + The ID of the thread where the log entry is generated. + +.EXAMPLE + $object | Write-PodeLog -Name 'LogName' .EXAMPLE -$object | Write-PodeLog -Name 'LogName' + Write-PodeLog -Name 'CustomLog' -Level 'Error' -Message 'An error occurred.' #> function Write-PodeLog { - [CmdletBinding()] + [CmdletBinding(DefaultParameterSetName = 'inbuilt')] param( [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'inbuilt')] [object] - $InputObject + $InputObject, + + [Parameter( ParameterSetName = 'custom')] + [string] + $Level = 'Informational', + + [Parameter( Mandatory = $true, ParameterSetName = 'custom')] + [string] + $Message, + + [Parameter( ParameterSetName = 'custom')] + [string] + $Tag = '-', + + [Parameter()] + [int] + $ThreadId + ) + Process { + # do nothing if logging is disabled, or logger isn't setup + if (!(Test-PodeLoggerEnabled -Name $Name)) { + return + } - # do nothing if logging is disabled, or logger isn't setup - if (!(Test-PodeLoggerEnabled -Name $Name)) { - return - } + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'inbuilt' { + $logItem = @{ + Name = $Name + Item = $InputObject + } + break + } + 'custom' { + $logItem = @{ + Name = $Name + Item = @{ + Level = $Level + Message = $Message + Tag = $Tag + } + } + break + } + } + $log = $PodeContext.Server.Logging.Type[$Name] + if ($log.Standard) { + $logItem.Item.Server = $PodeContext.Server.ComputerName + + if ($log.Method.Arguments.AsUTC) { + $logItem.Item.Date = [datetime]::UtcNow + } + else { + $logItem.Item.Date = [datetime]::Now + } - # add the item to be processed - $null = $PodeContext.LogsToProcess.Add(@{ - Name = $Name - Item = $InputObject - }) + if ($ThreadId) { + $logItem.Item.ThreadId = $ThreadId + } + else { + $logItem.Item.ThreadId = [System.Threading.Thread]::CurrentThread.ManagedThreadId + } + } + # add the item to be processed + [Pode.PodeLogger]::Enqueue($logItem) + } } <# .SYNOPSIS -Masks values within a log item to protect sensitive information. + Masks values within a log item to protect sensitive information. .DESCRIPTION -Masks values within a log item, or any string, to protect sensitive information. -Patterns, and the Mask, can be configured via the server.psd1 configuration file. + Masks values within a log item, or any string, to protect sensitive information. + Patterns, and the Mask, can be configured via the server.psd1 configuration file. .PARAMETER Item -The string Item to mask values. + The string Item to mask values. .EXAMPLE -$value = Protect-PodeLogItem -Item 'Username=Morty, Password=Hunter2' + $value = Protect-PodeLogItem -Item 'Username=Morty, Password=Hunter2' #> function Protect-PodeLogItem { [CmdletBinding()] @@ -666,54 +1421,56 @@ function Protect-PodeLogItem { $Item ) - # do nothing if there are no masks - if (Test-PodeIsEmpty $PodeContext.Server.Logging.Masking.Patterns) { - return $item - } + Process { + # do nothing if there are no masks + if (Test-PodeIsEmpty $PodeContext.Server.Logging.Masking.Patterns) { + return $item + } - # attempt to apply each mask - foreach ($mask in $PodeContext.Server.Logging.Masking.Patterns) { - if ($Item -imatch $mask) { - # has both keep before/after - if ($Matches.ContainsKey('keep_before') -and $Matches.ContainsKey('keep_after')) { - $Item = ($Item -ireplace $mask, "`${keep_before}$($PodeContext.Server.Logging.Masking.Mask)`${keep_after}") - } + # attempt to apply each mask + foreach ($mask in $PodeContext.Server.Logging.Masking.Patterns) { + if ($Item -imatch $mask) { + # has both keep before/after + if ($Matches.ContainsKey('keep_before') -and $Matches.ContainsKey('keep_after')) { + $Item = ($Item -ireplace $mask, "`${keep_before}$($PodeContext.Server.Logging.Masking.Mask)`${keep_after}") + } - # has just keep before - elseif ($Matches.ContainsKey('keep_before')) { - $Item = ($Item -ireplace $mask, "`${keep_before}$($PodeContext.Server.Logging.Masking.Mask)") - } + # has just keep before + elseif ($Matches.ContainsKey('keep_before')) { + $Item = ($Item -ireplace $mask, "`${keep_before}$($PodeContext.Server.Logging.Masking.Mask)") + } - # has just keep after - elseif ($Matches.ContainsKey('keep_after')) { - $Item = ($Item -ireplace $mask, "$($PodeContext.Server.Logging.Masking.Mask)`${keep_after}") - } + # has just keep after + elseif ($Matches.ContainsKey('keep_after')) { + $Item = ($Item -ireplace $mask, "$($PodeContext.Server.Logging.Masking.Mask)`${keep_after}") + } - # normal mask - else { - $Item = ($Item -ireplace $mask, $PodeContext.Server.Logging.Masking.Mask) + # normal mask + else { + $Item = ($Item -ireplace $mask, $PodeContext.Server.Logging.Masking.Mask) + } } } - } - return $Item + return $Item + } } <# .SYNOPSIS -Automatically loads logging ps1 files + Automatically loads logging ps1 files .DESCRIPTION -Automatically loads logging ps1 files from either a /logging folder, or a custom folder. Saves space dot-sourcing them all one-by-one. + Automatically loads logging ps1 files from either a /logging folder, or a custom folder. Saves space dot-sourcing them all one-by-one. .PARAMETER Path -Optional Path to a folder containing ps1 files, can be relative or literal. + Optional Path to a folder containing ps1 files, can be relative or literal. .EXAMPLE -Use-PodeLogging + Use-PodeLogging .EXAMPLE -Use-PodeLogging -Path './my-logging' + Use-PodeLogging -Path './my-logging' #> function Use-PodeLogging { [CmdletBinding()] @@ -724,4 +1481,88 @@ function Use-PodeLogging { ) Use-PodeFolder -Path $Path -DefaultPath 'logging' +} + +<# +.SYNOPSIS + Enables logging in Pode. + +.DESCRIPTION + This function enables logging in Pode by setting the appropriate flags in the Pode context. + +.PARAMETER Terminal + A switch parameter that, if specified, enables terminal logging for the Pode C# listener. + +.EXAMPLE + Enable-PodeLogging + This example enables all logging except terminal logging. + +.EXAMPLE + Enable-PodeLogging -Terminal + This example enables all logging including terminal logging for the Pode C# listener. +#> +function Enable-PodeLogging { + param( + [switch] + $Terminal + ) + + # Enable Pode logging + [pode.PodeLogger]::Enabled = $true + $PodeContext.Server.Logging.Enabled = $true + + # Enable terminal logging for the Pode C# listener if the Terminal switch is specified + [pode.PodeLogger]::Terminal = $Terminal.IsPresent +} + + +<# +.SYNOPSIS + Disables logging in Pode. + +.DESCRIPTION + This function disables logging in Pode by setting the appropriate flags in the Pode context. + It allows you to optionally keep terminal logging enabled. + +.PARAMETER KeepTerminal + A switch parameter that, if specified, keeps terminal logging enabled for the Pode C# listener even when other logging is disabled. + +.EXAMPLE + Disable-PodeLogging + This example disables all logging including terminal logging. + +.EXAMPLE + Disable-PodeLogging -KeepTerminal + This example disables all logging except terminal logging. +#> +function Disable-PodeLogging { + param( + [switch] + $KeepTerminal + ) + + # Disable Pode logging + [pode.PodeLogger]::Enabled = $false + $PodeContext.Server.Logging.Enabled = $false + + # Optionally disable terminal logging if the KeepTerminal switch is not specified + if (! $KeepTerminal.IsPresent) { + [pode.PodeLogger]::Terminal = $false + } +} + + + +<# +.SYNOPSIS + Clears the Pode logging. + +.DESCRIPTION + This function clears all the logs in Pode by calling the Clear method on the PodeLogger class. + +.EXAMPLE + Clear-PodeLogging +#> +function Clear-PodeLogging { + [pode.PodeLogger]::Clear() } \ No newline at end of file diff --git a/src/Public/Middleware.ps1 b/src/Public/Middleware.ps1 index a279c00fe..8998a8ad9 100644 --- a/src/Public/Middleware.ps1 +++ b/src/Public/Middleware.ps1 @@ -38,6 +38,9 @@ function Add-PodeAccessRule { $Values ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # error if serverless Test-PodeIsServerless -FunctionName 'Add-PodeAccessRule' -ThrowError @@ -106,6 +109,9 @@ function Add-PodeLimitRule { $Group ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # call the appropriate limit method foreach ($value in $Values) { switch ($Type.ToLowerInvariant()) { @@ -360,6 +366,9 @@ function Add-PodeBodyParser { $ScriptBlock ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # if a parser for the type already exists, fail if ($PodeContext.Server.BodyParsers.ContainsKey($ContentType)) { # A body-parser is already defined for the content-type @@ -397,6 +406,9 @@ function Remove-PodeBodyParser { $ContentType ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # if there's no parser for the type, return if (!$PodeContext.Server.BodyParsers.ContainsKey($ContentType)) { return @@ -460,6 +472,9 @@ function Add-PodeMiddleware { $ArgumentList ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # ensure name doesn't already exist if (($PodeContext.Server.Middleware | Where-Object { $_.Name -ieq $Name } | Measure-Object).Count -gt 0) { # [Middleware] Name: Middleware already defined @@ -561,6 +576,9 @@ function Remove-PodeMiddleware { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $PodeContext.Server.Middleware = @($PodeContext.Server.Middleware | Where-Object { $_.Name -ine $Name }) } @@ -578,6 +596,9 @@ function Clear-PodeMiddleware { [CmdletBinding()] param() + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $PodeContext.Server.Middleware = @() } diff --git a/src/Public/OAComponents.ps1 b/src/Public/OAComponents.ps1 index b0ccca20c..0d8ec4228 100644 --- a/src/Public/OAComponents.ps1 +++ b/src/Public/OAComponents.ps1 @@ -78,7 +78,12 @@ function Add-PodeOAComponentResponse { [string[]] $DefinitionTag ) + + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag + foreach ($tag in $DefinitionTag) { $PodeContext.Server.OpenAPI.Definitions[$tag].components.responses[$Name] = New-PodeOResponseInternal -DefinitionTag $tag -Params $PSBoundParameters } @@ -141,6 +146,9 @@ function Add-PodeOAComponentSchema { $DefinitionTag ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag foreach ($tag in $DefinitionTag) { @@ -227,6 +235,9 @@ function Add-PodeOAComponentHeader { $DefinitionTag ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag foreach ($tag in $DefinitionTag) { @@ -309,6 +320,9 @@ function Add-PodeOAComponentRequestBody { $DefinitionTag ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag foreach ($tag in $DefinitionTag) { @@ -373,6 +387,9 @@ function Add-PodeOAComponentParameter { $DefinitionTag ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag foreach ($tag in $DefinitionTag) { @@ -452,7 +469,12 @@ function Add-PodeOAComponentExample { [string[]] $DefinitionTag ) + + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag + foreach ($tag in $DefinitionTag) { $Example = [ordered]@{ } if ($Summary) { @@ -884,7 +906,12 @@ function Remove-PodeOAComponent { [string[]] $DefinitionTag ) + + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag + foreach ($tag in $DefinitionTag) { if (!($PodeContext.Server.OpenAPI.Definitions[$tag].components[$field ].keys -ccontains $Name)) { $PodeContext.Server.OpenAPI.Definitions[$tag].components[$field ].remove($Name) @@ -903,4 +930,3 @@ if (!(Test-Path Alias:Enable-PodeOA)) { if (!(Test-Path Alias:Get-PodeOpenApiDefinition)) { New-Alias Get-PodeOpenApiDefinition -Value Get-PodeOADefinition } - diff --git a/src/Public/OpenApi.ps1 b/src/Public/OpenApi.ps1 index 77dd0a4d4..33ea5d73d 100644 --- a/src/Public/OpenApi.ps1 +++ b/src/Public/OpenApi.ps1 @@ -395,6 +395,9 @@ function Add-PodeOAServerEndpoint { $DefinitionTag ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if (Test-PodeIsEmpty -Value $DefinitionTag) { $DefinitionTag = @($PodeContext.Server.OpenAPI.SelectedDefinitionTag) } @@ -627,6 +630,9 @@ function Add-PodeOAResponse { $DefinitionTag ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if ($null -eq $Route) { # The parameter 'Route' cannot be null throw ($PodeLocale.routeParameterCannotBeNullExceptionMessage) @@ -703,6 +709,9 @@ function Remove-PodeOAResponse { $PassThru ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if ($null -eq $Route) { # The parameter 'Route' cannot be null throw ($PodeLocale.routeParameterCannotBeNullExceptionMessage) @@ -767,6 +776,9 @@ function Set-PodeOARequest { $PassThru ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if ($null -eq $Route) { # The parameter 'Route' cannot be null throw ($PodeLocale.routeParameterCannotBeNullExceptionMessage) @@ -1566,6 +1578,9 @@ function Set-PodeOARouteInfo { $DefinitionTag ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if ($null -eq $Route) { # The parameter 'Route' cannot be null throw ($PodeLocale.routeParameterCannotBeNullExceptionMessage) @@ -2001,6 +2016,9 @@ function Add-PodeOAExternalDoc { $DefinitionTag ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag foreach ($tag in $DefinitionTag) { @@ -2061,6 +2079,9 @@ function Add-PodeOATag { $DefinitionTag ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag foreach ($tag in $DefinitionTag) { @@ -2167,6 +2188,9 @@ function Add-PodeOAInfo { $DefinitionTag ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag $Info = [ordered]@{} @@ -2607,6 +2631,9 @@ function Add-PodeOACallBack { $DefinitionTag ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if ($null -eq $Route) { # The parameter 'Route' cannot be null throw ($PodeLocale.routeParameterCannotBeNullExceptionMessage) @@ -3190,6 +3217,9 @@ function Add-PodeOAExternalRoute { $DefinitionTag ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { @@ -3358,6 +3388,9 @@ function Add-PodeOAWebhook { $DefinitionTag ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag $refRoute = @{ diff --git a/src/Public/Responses.ps1 b/src/Public/Responses.ps1 index e46bb932a..b3410f599 100644 --- a/src/Public/Responses.ps1 +++ b/src/Public/Responses.ps1 @@ -1535,6 +1535,9 @@ function Set-PodeViewEngine { $Extension ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # truncate markdown if ($Type -ieq 'Markdown') { $Type = 'md' @@ -1751,6 +1754,9 @@ function Add-PodeViewFolder { $Source ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # ensure the folder doesn't already exist if ($PodeContext.Server.Views.ContainsKey($Name)) { # The Views folder name already exists diff --git a/src/Public/Routes.ps1 b/src/Public/Routes.ps1 index ffed8e91f..55eec2c4e 100644 --- a/src/Public/Routes.ps1 +++ b/src/Public/Routes.ps1 @@ -208,6 +208,9 @@ function Add-PodeRoute { $OADefinitionTag ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # check if we have any route group info defined if ($null -ne $RouteGroup) { if (![string]::IsNullOrWhiteSpace($RouteGroup.Path)) { @@ -644,6 +647,9 @@ function Add-PodeStaticRoute { $RedirectToDefault ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # check if we have any route group info defined if ($null -ne $RouteGroup) { if (![string]::IsNullOrWhiteSpace($RouteGroup.Path)) { @@ -958,6 +964,9 @@ function Add-PodeSignalRoute { $IfExists = 'Default' ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # check if we have any route group info defined if ($null -ne $RouteGroup) { if (![string]::IsNullOrWhiteSpace($RouteGroup.Path)) { @@ -1179,6 +1188,9 @@ function Add-PodeRouteGroup { $OADefinitionTag ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if (Test-PodeIsEmpty $Routes) { # The Route parameter needs a valid, not empty, scriptblock throw ($PodeLocale.routeParameterNeedsValidScriptblockExceptionMessage) @@ -1437,6 +1449,9 @@ function Add-PodeStaticRouteGroup { $RedirectToDefault ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if (Test-PodeIsEmpty $Routes) { # The Route parameter needs a valid, not empty, scriptblock throw ($PodeLocale.routeParameterNeedsValidScriptblockExceptionMessage) @@ -1605,6 +1620,9 @@ function Add-PodeSignalRouteGroup { $IfExists = 'Default' ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if (Test-PodeIsEmpty $Routes) { # The Route parameter needs a valid, not empty, scriptblock throw ($PodeLocale.routeParameterNeedsValidScriptblockExceptionMessage) @@ -1681,6 +1699,9 @@ function Remove-PodeRoute { $EndpointName ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # split route on '?' for query $Path = Split-PodeRouteQuery -Path $Path @@ -1746,6 +1767,9 @@ function Remove-PodeStaticRoute { $EndpointName ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $Method = 'Static' # ensure the route has appropriate slashes and replace parameters @@ -1795,6 +1819,9 @@ function Remove-PodeSignalRoute { $EndpointName ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $Method = 'Signal' # ensure the route has appropriate slashes and replace parameters @@ -1841,6 +1868,9 @@ function Clear-PodeRoutes { $Method ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if (![string]::IsNullOrWhiteSpace($Method)) { $PodeContext.Server.Routes[$Method].Clear() } @@ -1865,6 +1895,9 @@ function Clear-PodeStaticRoutes { [CmdletBinding()] param() + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $PodeContext.Server.Routes['Static'].Clear() } @@ -1882,6 +1915,9 @@ function Clear-PodeSignalRoutes { [CmdletBinding()] param() + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $PodeContext.Server.Routes['Signal'].Clear() } @@ -2260,6 +2296,9 @@ function Add-PodePage { $FlashMessages ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $logic = $null $arg = $null @@ -2621,6 +2660,9 @@ function Set-PodeRouteIfExistsPreference { $Value = 'Default' ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $PodeContext.Server.Preferences.Routes.IfExists = $Value } diff --git a/src/Public/SSE.ps1 b/src/Public/SSE.ps1 index 07b708612..26feec6ff 100644 --- a/src/Public/SSE.ps1 +++ b/src/Public/SSE.ps1 @@ -130,6 +130,9 @@ function Set-PodeSseDefaultScope { $Scope ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $PodeContext.Server.Sse.DefaultScope = $Scope } @@ -599,6 +602,9 @@ function Set-PodeSseBroadcastLevel { $Type ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $PodeContext.Server.Sse.BroadcastLevel[$Name] = $Type.ToLowerInvariant() } diff --git a/src/Public/Schedules.ps1 b/src/Public/Schedules.ps1 index 2b74cb538..8a2f4983a 100644 --- a/src/Public/Schedules.ps1 +++ b/src/Public/Schedules.ps1 @@ -83,6 +83,9 @@ function Add-PodeSchedule { $OnStart ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # error if serverless Test-PodeIsServerless -FunctionName 'Add-PodeSchedule' -ThrowError @@ -161,6 +164,9 @@ function Set-PodeScheduleConcurrency { $Maximum ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # error if <=0 if ($Maximum -le 0) { # Maximum concurrent schedules must be >=1 but got @@ -244,6 +250,9 @@ function Remove-PodeSchedule { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $null = $PodeContext.Schedules.Items.Remove($Name) } @@ -261,6 +270,9 @@ function Clear-PodeSchedules { [CmdletBinding()] param() + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $PodeContext.Schedules.Items.Clear() } diff --git a/src/Public/ScopedVariables.ps1 b/src/Public/ScopedVariables.ps1 index b268a54fc..a9723617d 100644 --- a/src/Public/ScopedVariables.ps1 +++ b/src/Public/ScopedVariables.ps1 @@ -239,6 +239,9 @@ function Add-PodeScopedVariable { $ScriptBlock ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + Add-PodeScopedVariableInternal @PSBoundParameters } @@ -263,6 +266,9 @@ function Remove-PodeScopedVariable { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $null = $PodeContext.Server.ScopedVariables.Remove($Name) } @@ -301,6 +307,10 @@ Removes all Scoped Variables. Clear-PodeScopedVariables #> function Clear-PodeScopedVariables { + + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $null = $PodeContext.Server.ScopedVariables.Clear() } diff --git a/src/Public/Secrets.ps1 b/src/Public/Secrets.ps1 index ac87f3fa3..031e94f70 100644 --- a/src/Public/Secrets.ps1 +++ b/src/Public/Secrets.ps1 @@ -744,6 +744,9 @@ function Remove-PodeSecret { $ArgumentList ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # has the vault been registered? if (!(Test-PodeSecretVault -Name $Vault)) { # No Secret Vault with the name has been registered diff --git a/src/Public/Security.ps1 b/src/Public/Security.ps1 index 5a928c9e0..38a0fd0e6 100644 --- a/src/Public/Security.ps1 +++ b/src/Public/Security.ps1 @@ -132,6 +132,9 @@ function Add-PodeSecurityHeader { $Append ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if ([string]::IsNullOrWhiteSpace($Value)) { return } @@ -172,6 +175,9 @@ function Remove-PodeSecurityHeader { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $PodeContext.Server.Security.Headers.Remove($Name) } @@ -517,6 +523,9 @@ function Add-PodeSecurityContentSecurityPolicy { $UpgradeInsecureRequests ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + Set-PodeSecurityContentSecurityPolicyInternal -Params $PSBoundParameters -Append } diff --git a/src/Public/Sessions.ps1 b/src/Public/Sessions.ps1 index 5ffbbb80c..43566fdd2 100644 --- a/src/Public/Sessions.ps1 +++ b/src/Public/Sessions.ps1 @@ -180,6 +180,9 @@ function Remove-PodeSession { [CmdletBinding()] param() + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # if sessions haven't been setup, error if (!(Test-PodeSessionsEnabled)) { # The sessions have not been configured diff --git a/src/Public/State.ps1 b/src/Public/State.ps1 index 3ca7face2..857bda84e 100644 --- a/src/Public/State.ps1 +++ b/src/Public/State.ps1 @@ -37,6 +37,9 @@ function Set-PodeState { $Scope ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if ($null -eq $PodeContext.Server.State) { # Pode has not been initialized throw ($PodeLocale.podeNotInitializedExceptionMessage) @@ -178,6 +181,9 @@ function Remove-PodeState { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if ($null -eq $PodeContext.Server.State) { # Pode has not been initialized throw ($PodeLocale.podeNotInitializedExceptionMessage) diff --git a/src/Public/Tasks.ps1 b/src/Public/Tasks.ps1 index 0f46cbded..48634a035 100644 --- a/src/Public/Tasks.ps1 +++ b/src/Public/Tasks.ps1 @@ -42,6 +42,10 @@ function Add-PodeTask { [hashtable] $ArgumentList ) + + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # ensure the task doesn't already exist if ($PodeContext.Tasks.Items.ContainsKey($Name)) { # [Task] Task already defined @@ -87,6 +91,9 @@ function Set-PodeTaskConcurrency { $Maximum ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # error if <=0 if ($Maximum -le 0) { # Maximum concurrent tasks must be >=1 but got @@ -198,6 +205,9 @@ function Remove-PodeTask { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $null = $PodeContext.Tasks.Items.Remove($Name) } @@ -215,6 +225,9 @@ function Clear-PodeTasks { [CmdletBinding()] param() + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $PodeContext.Tasks.Items.Clear() } diff --git a/src/Public/Threading.ps1 b/src/Public/Threading.ps1 index 0438773f5..9440a0d8b 100644 --- a/src/Public/Threading.ps1 +++ b/src/Public/Threading.ps1 @@ -137,6 +137,9 @@ function Remove-PodeLockable { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if (Test-PodeLockable -Name $Name) { $PodeContext.Threading.Lockables.Custom.Remove($Name) } @@ -343,6 +346,9 @@ function Clear-PodeLockables { [CmdletBinding()] param() + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if (Test-PodeIsEmpty $PodeContext.Threading.Lockables.Custom) { return } @@ -483,6 +489,9 @@ function Remove-PodeMutex { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if (Test-PodeMutex -Name $Name) { $PodeContext.Threading.Mutexes[$Name].Dispose() $PodeContext.Threading.Mutexes.Remove($Name) @@ -634,6 +643,9 @@ function Clear-PodeMutexes { [CmdletBinding()] param() + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if (Test-PodeIsEmpty $PodeContext.Threading.Mutexes) { return } @@ -785,6 +797,9 @@ function Remove-PodeSemaphore { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if (Test-PodeSemaphore -Name $Name) { $PodeContext.Threading.Semaphores[$Name].Dispose() $PodeContext.Threading.Semaphores.Remove($Name) @@ -947,6 +962,9 @@ function Clear-PodeSemaphores { [CmdletBinding()] param() + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if (Test-PodeIsEmpty $PodeContext.Threading.Semaphores) { return } diff --git a/src/Public/Timers.ps1 b/src/Public/Timers.ps1 index 8042bca71..f62629547 100644 --- a/src/Public/Timers.ps1 +++ b/src/Public/Timers.ps1 @@ -76,6 +76,9 @@ function Add-PodeTimer { $OnStart ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # error if serverless Test-PodeIsServerless -FunctionName 'Add-PodeTimer' -ThrowError @@ -195,6 +198,9 @@ function Remove-PodeTimer { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $null = $PodeContext.Timers.Items.Remove($Name) } @@ -212,6 +218,9 @@ function Clear-PodeTimers { [CmdletBinding()] param() + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $PodeContext.Timers.Items.Clear() } @@ -259,7 +268,7 @@ function Edit-PodeTimer { # ensure the timer exists if (!$PodeContext.Timers.Items.ContainsKey($Name)) { - # Timer 'Name' does not exist + # Timer 'Name' does not exist throw ($PodeLocale.timerDoesNotExistExceptionMessage -f $Name) } diff --git a/src/Public/Utilities.ps1 b/src/Public/Utilities.ps1 index dde5ff1bb..54fac268e 100644 --- a/src/Public/Utilities.ps1 +++ b/src/Public/Utilities.ps1 @@ -99,6 +99,9 @@ function Start-PodeStopwatch { $ScriptBlock ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + try { $watch = [System.Diagnostics.Stopwatch]::StartNew() . $ScriptBlock @@ -221,72 +224,6 @@ function Get-PodeConfig { return $PodeContext.Server.Configuration } -<# -.SYNOPSIS -Adds a ScriptBlock as Endware to run at the end of each web Request. - -.DESCRIPTION -Adds a ScriptBlock as Endware to run at the end of each web Request. - -.PARAMETER ScriptBlock -The ScriptBlock to add. It will be supplied the current web event. - -.PARAMETER ArgumentList -An array of arguments to supply to the Endware's ScriptBlock. - -.EXAMPLE -Add-PodeEndware -ScriptBlock { /* logic */ } -#> -function Add-PodeEndware { - [CmdletBinding()] - param( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [scriptblock] - $ScriptBlock, - - [Parameter()] - [object[]] - $ArgumentList - ) - - # check for scoped vars - $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState - - # add the scriptblock to array of endware that needs to be run - $PodeContext.Server.Endware += @{ - Logic = $ScriptBlock - UsingVariables = $usingVars - Arguments = $ArgumentList - } -} - -<# -.SYNOPSIS -Automatically loads endware ps1 files - -.DESCRIPTION -Automatically loads endware ps1 files from either a /endware folder, or a custom folder. Saves space dot-sourcing them all one-by-one. - -.PARAMETER Path -Optional Path to a folder containing ps1 files, can be relative or literal. - -.EXAMPLE -Use-PodeEndware - -.EXAMPLE -Use-PodeEndware -Path './endware' -#> -function Use-PodeEndware { - [CmdletBinding()] - param( - [Parameter()] - [string] - $Path - ) - - Use-PodeFolder -Path $Path -DefaultPath 'endware' -} - <# .SYNOPSIS Imports a Module into the current, and all runspaces that Pode uses. diff --git a/src/Public/Verbs.ps1 b/src/Public/Verbs.ps1 index 93c91ff59..0eded746b 100644 --- a/src/Public/Verbs.ps1 +++ b/src/Public/Verbs.ps1 @@ -69,6 +69,9 @@ function Add-PodeVerb { $Close ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # find placeholder parameters in verb (ie: COMMAND :parameter) $Verb = Resolve-PodePlaceholder -Path $Verb @@ -146,6 +149,9 @@ function Remove-PodeVerb { $EndpointName ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # ensure the verb placeholders are replaced $Verb = Resolve-PodePlaceholder -Path $Verb @@ -179,6 +185,9 @@ function Clear-PodeVerbs { [CmdletBinding()] param() + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + $PodeContext.Server.Verbs.Clear() } diff --git a/src/Public/WebSockets.ps1 b/src/Public/WebSockets.ps1 index e1d9f4958..5c92bb593 100644 --- a/src/Public/WebSockets.ps1 +++ b/src/Public/WebSockets.ps1 @@ -21,6 +21,9 @@ function Set-PodeWebSocketConcurrency { $Maximum ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + # error if <=0 if ($Maximum -le 0) { # Maximum concurrent WebSocket threads must be >=1 but got @@ -205,6 +208,9 @@ function Remove-PodeWebSocket { $Name ) + # Record the operation on the trace log + Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters + if ([string]::IsNullOrWhiteSpace($Name) -and ($null -ne $WsEvent)) { $Name = $WsEvent.Request.WebSocket.Name } diff --git a/tests/unit/Authentication.Tests.ps1 b/tests/unit/Authentication.Tests.ps1 index 38190f523..d27bdbfbd 100644 --- a/tests/unit/Authentication.Tests.ps1 +++ b/tests/unit/Authentication.Tests.ps1 @@ -5,6 +5,9 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} } $now = [datetime]::UtcNow diff --git a/tests/unit/Context.Tests.ps1 b/tests/unit/Context.Tests.ps1 index 6006e6428..be6879892 100644 --- a/tests/unit/Context.Tests.ps1 +++ b/tests/unit/Context.Tests.ps1 @@ -5,6 +5,9 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} $PodeContext = @{ 'Server' = $null; } } diff --git a/tests/unit/Cookies.Tests.ps1 b/tests/unit/Cookies.Tests.ps1 index 294901e9f..5e4e6a55c 100644 --- a/tests/unit/Cookies.Tests.ps1 +++ b/tests/unit/Cookies.Tests.ps1 @@ -5,6 +5,9 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} } Describe 'Test-PodeCookie' { It 'Returns true' { diff --git a/tests/unit/CronParser.Tests.ps1 b/tests/unit/CronParser.Tests.ps1 index 5692d6324..bc79c670e 100644 --- a/tests/unit/CronParser.Tests.ps1 +++ b/tests/unit/CronParser.Tests.ps1 @@ -5,6 +5,9 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} } Describe 'Get-PodeCronField' { diff --git a/tests/unit/Cryptography.Tests.ps1 b/tests/unit/Cryptography.Tests.ps1 index 423446f27..a6d424eae 100644 --- a/tests/unit/Cryptography.Tests.ps1 +++ b/tests/unit/Cryptography.Tests.ps1 @@ -3,6 +3,9 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} } Describe 'Invoke-PodeHMACSHA256Hash' { diff --git a/tests/unit/Endware.Tests.ps1 b/tests/unit/Endware.Tests.ps1 index c3e80ae0c..d9560f4ac 100644 --- a/tests/unit/Endware.Tests.ps1 +++ b/tests/unit/Endware.Tests.ps1 @@ -5,6 +5,9 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} } Describe 'Invoke-PodeEndware' { diff --git a/tests/unit/Flash.Tests.ps1 b/tests/unit/Flash.Tests.ps1 index 3996553c2..92b53f730 100644 --- a/tests/unit/Flash.Tests.ps1 +++ b/tests/unit/Flash.Tests.ps1 @@ -6,6 +6,9 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} } Describe 'Add-PodeFlashMessage' { diff --git a/tests/unit/Handlers.Tests.ps1 b/tests/unit/Handlers.Tests.ps1 index 29dd6ce09..6432787ed 100644 --- a/tests/unit/Handlers.Tests.ps1 +++ b/tests/unit/Handlers.Tests.ps1 @@ -7,6 +7,9 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} $PodeContext = @{ 'Server' = $null; } } diff --git a/tests/unit/Headers.Tests.ps1 b/tests/unit/Headers.Tests.ps1 index a666ce642..6c4a923c6 100644 --- a/tests/unit/Headers.Tests.ps1 +++ b/tests/unit/Headers.Tests.ps1 @@ -5,6 +5,9 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} } Describe 'Test-PodeHeader' { Context 'WebServer' { diff --git a/tests/unit/Helpers.Tests.ps1 b/tests/unit/Helpers.Tests.ps1 index e9d9cb76f..df8667c85 100644 --- a/tests/unit/Helpers.Tests.ps1 +++ b/tests/unit/Helpers.Tests.ps1 @@ -2,11 +2,14 @@ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '')] param() BeforeAll { - Add-Type -AssemblyName "System.Net.Http" -ErrorAction SilentlyContinue + Add-Type -AssemblyName 'System.Net.Http' -ErrorAction SilentlyContinue $path = $PSCommandPath $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} } Describe 'Get-PodeType' { @@ -770,7 +773,7 @@ Describe 'Get-PodeEndpointInfo' { } It 'Throws an error for an invalid IP endpoint' { - { Get-PodeEndpointInfo -Address '700.0.0.a' } | Should -Throw -ExpectedMessage ($PodeLocale.failedToParseAddressExceptionMessage -f '700.0.0.a' ) #'*Failed to parse*' + { Get-PodeEndpointInfo -Address '700.0.0.a' } | Should -Throw -ExpectedMessage ($PodeLocale.failedToParseAddressExceptionMessage -f '700.0.0.a' ) #'*Failed to parse*' } It 'Throws an error for an out-of-range IP endpoint' { @@ -1142,7 +1145,9 @@ Describe 'Close-PodeServerInternal' { Mock Stop-PodeFileMonitor { } Mock Close-PodeDisposable { } Mock Remove-PodePSDrive { } - Mock Write-Host { } } + Mock Write-Host { } + Mock Disable-PodeLogging { } + } It 'Closes out pode, but with no done flag' { $PodeContext = @{ 'Server' = @{ 'Types' = 'Server' } } @@ -1766,4 +1771,4 @@ Describe 'ConvertTo-PodeYamlInternal Tests' { $result | Should -Be '' } } -} +} \ No newline at end of file diff --git a/tests/unit/Logging.Tests.ps1 b/tests/unit/Logging.Tests.ps1 index c7cbf1991..8f7eff11d 100644 --- a/tests/unit/Logging.Tests.ps1 +++ b/tests/unit/Logging.Tests.ps1 @@ -5,59 +5,88 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + if (!([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq 'Pode' })) { + Add-Type -LiteralPath "$($src)/Libs/netstandard2.0/Pode.dll" -ErrorAction Stop + } + [Pode.PodeLogger]::Enabled = $true + } Describe 'Get-PodeLogger' { It 'Returns null as the logger does not exist' { - $PodeContext = @{ 'Server' = @{ 'Logging' = @{ 'Types' = @{}; } }; } + $PodeContext = @{ 'Server' = @{ 'Logging' = @{ 'Type' = @{}; } }; } Get-PodeLogger -Name 'test' | Should -Be $null } It 'Returns terminal logger for name' { - $PodeContext = @{ 'Server' = @{ 'Logging' = @{ 'Types' = @{ 'test' = $null }; } }; } + $PodeContext = @{ 'Server' = @{ 'Logging' = @{ 'Type' = @{ 'test' = $null }; } }; } $result = (Get-PodeLogger -Name 'test') $result | Should -Be $null } It 'Returns custom logger for name' { - $PodeContext = @{ 'Server' = @{ 'Logging' = @{ 'Types' = @{ 'test' = { Write-Host 'hello' } }; } }; } + $PodeContext = @{ 'Server' = @{ 'Logging' = @{ 'Type' = @{ 'test' = { Write-PodeHost 'hello' } }; } }; } $result = (Get-PodeLogger -Name 'test') $result | Should -Not -Be $null - $result.ToString() | Should -Be ({ Write-Host 'hello' }).ToString() + $result.ToString() | Should -Be ({ Write-PodeHost 'hello' }).ToString() } } Describe 'Write-PodeLog' { + BeforeEach { + $PodeContext = @{ + Server = @{ + Logging = @{ + LogsToProcess = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + Type = @{ + test = @{ + Standard = $false + } + } + } + } + } + } It 'Does nothing when logging disabled' { Mock Test-PodeLoggerEnabled { return $false } - $PodeContext = @{ LogsToProcess = New-Object System.Collections.ArrayList } Write-PodeLog -Name 'test' -InputObject 'test' - $PodeContext.LogsToProcess.Count | Should -Be 0 + [Pode.PodeLogger]::Count | Should -Be 0 } It 'Adds a log item' { Mock Test-PodeLoggerEnabled { return $true } - $PodeContext = @{ LogsToProcess = New-Object System.Collections.ArrayList } - Write-PodeLog -Name 'test' -InputObject 'test' - $PodeContext.LogsToProcess.Count | Should -Be 1 - $PodeContext.LogsToProcess[0].Name | Should -Be 'test' - $PodeContext.LogsToProcess[0].Item | Should -Be 'test' + [Pode.PodeLogger]::Count | Should -Be 1 + $item = [Pode.PodeLogger]::Dequeue() + $item.Name | Should -Be 'test' + $item.Item | Should -Be 'test' } } Describe 'Write-PodeErrorLog' { + BeforeEach { + $PodeContext = @{ + Server = @{ + Logging = @{ + Type = @{ + test = @{ + Standard = $false + } + } + } + } + } + } It 'Does nothing when logging disabled' { Mock Test-PodeLoggerEnabled { return $false } - $PodeContext = @{ LogsToProcess = New-Object System.Collections.ArrayList } Write-PodeLog -Name 'test' -InputObject 'test' - $PodeContext.LogsToProcess.Count | Should -Be 0 + [Pode.PodeLogger]::Count | Should -Be 0 } It 'Adds an error log item' { @@ -65,17 +94,18 @@ Describe 'Write-PodeErrorLog' { Mock Get-PodeLogger { return @{ Arguments = @{ Levels = @('Error') } - } } + } + } - $PodeContext = @{ LogsToProcess = New-Object System.Collections.ArrayList } try { throw 'some error' } catch { Write-PodeErrorLog -ErrorRecord $Error[0] } - $PodeContext.LogsToProcess.Count | Should -Be 1 - $PodeContext.LogsToProcess[0].Item.Message | Should -Be 'some error' + [Pode.PodeLogger]::Count | Should -Be 1 + $item = [Pode.PodeLogger]::Dequeue() + $item.Item.Message | Should -Be 'some error' } It 'Adds an exception log item' { @@ -85,13 +115,12 @@ Describe 'Write-PodeErrorLog' { } } } - $PodeContext = @{ LogsToProcess = New-Object System.Collections.ArrayList } - $exp = [exception]::new('some error') Write-PodeErrorLog -Exception $exp - $PodeContext.LogsToProcess.Count | Should -Be 1 - $PodeContext.LogsToProcess[0].Item.Message | Should -Be 'some error' + [Pode.PodeLogger]::Count | Should -Be 1 + $item = [Pode.PodeLogger]::Dequeue() + $item.Item.Message | Should -Be 'some error' } It 'Does not log as Verbose not allowed' { @@ -101,12 +130,10 @@ Describe 'Write-PodeErrorLog' { } } } - $PodeContext = @{ LogsToProcess = New-Object System.Collections.ArrayList } - $exp = [exception]::new('some error') Write-PodeErrorLog -Exception $exp -Level Verbose - - $PodeContext.LogsToProcess.Count | Should -Be 0 + $item = [Pode.PodeLogger]::Dequeue() + [Pode.PodeLogger]::Count | Should -Be 0 } } diff --git a/tests/unit/Mappers.Tests.ps1 b/tests/unit/Mappers.Tests.ps1 index ab0446730..750b9bd9b 100644 --- a/tests/unit/Mappers.Tests.ps1 +++ b/tests/unit/Mappers.Tests.ps1 @@ -5,6 +5,9 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} } Describe 'Get-PodeContentType' { Context 'No extension supplied' { diff --git a/tests/unit/Metrics.Tests.ps1 b/tests/unit/Metrics.Tests.ps1 index 5dcbe9b05..698cae935 100644 --- a/tests/unit/Metrics.Tests.ps1 +++ b/tests/unit/Metrics.Tests.ps1 @@ -6,6 +6,9 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} + $PodeContext = @{ Metrics = @{ Server = @{ diff --git a/tests/unit/Middleware.Tests.ps1 b/tests/unit/Middleware.Tests.ps1 index 62c5ff9ec..abf2a36d9 100644 --- a/tests/unit/Middleware.Tests.ps1 +++ b/tests/unit/Middleware.Tests.ps1 @@ -7,6 +7,9 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} } Describe 'Get-PodeInbuiltMiddleware' { diff --git a/tests/unit/NameGenerator.Tests.ps1 b/tests/unit/NameGenerator.Tests.ps1 index 85a78f316..257c63632 100644 --- a/tests/unit/NameGenerator.Tests.ps1 +++ b/tests/unit/NameGenerator.Tests.ps1 @@ -3,6 +3,9 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} } Describe 'Get-PodeRandomName' { diff --git a/tests/unit/OpenApi.Tests.ps1 b/tests/unit/OpenApi.Tests.ps1 index aab9ca389..3720cdd0e 100644 --- a/tests/unit/OpenApi.Tests.ps1 +++ b/tests/unit/OpenApi.Tests.ps1 @@ -6,6 +6,9 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} } Describe 'OpenApi' { diff --git a/tests/unit/PrivateOpenApi.Tests.ps1 b/tests/unit/PrivateOpenApi.Tests.ps1 index ff07225a1..6ee52a291 100644 --- a/tests/unit/PrivateOpenApi.Tests.ps1 +++ b/tests/unit/PrivateOpenApi.Tests.ps1 @@ -6,6 +6,9 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} } Describe 'PrivateOpenApi' { diff --git a/tests/unit/Responses.Tests.ps1 b/tests/unit/Responses.Tests.ps1 index 80a10c741..08f701e6d 100644 --- a/tests/unit/Responses.Tests.ps1 +++ b/tests/unit/Responses.Tests.ps1 @@ -7,6 +7,9 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} + } Describe 'Set-PodeResponseStatus' { diff --git a/tests/unit/Routes.Tests.ps1 b/tests/unit/Routes.Tests.ps1 index dc5aff5ca..a4a808ea6 100644 --- a/tests/unit/Routes.Tests.ps1 +++ b/tests/unit/Routes.Tests.ps1 @@ -7,6 +7,9 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} $PodeContext = @{ 'Server' = $null; } } diff --git a/tests/unit/Schedules.Tests.ps1 b/tests/unit/Schedules.Tests.ps1 index ce426bbe0..3cb532328 100644 --- a/tests/unit/Schedules.Tests.ps1 +++ b/tests/unit/Schedules.Tests.ps1 @@ -7,6 +7,9 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} } Describe 'Find-PodeSchedule' { Context 'Invalid parameters supplied' { diff --git a/tests/unit/Security.Tests.ps1 b/tests/unit/Security.Tests.ps1 index c1311c401..606377575 100644 --- a/tests/unit/Security.Tests.ps1 +++ b/tests/unit/Security.Tests.ps1 @@ -8,6 +8,9 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} + $PodeContext = @{ 'Server' = $null; } } Describe 'Test-PodeIPAccess' { diff --git a/tests/unit/Server.Tests.ps1 b/tests/unit/Server.Tests.ps1 index 21a1a7514..fcaf8811c 100644 --- a/tests/unit/Server.Tests.ps1 +++ b/tests/unit/Server.Tests.ps1 @@ -7,12 +7,15 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - $PodeContext = @{ Server = $null Metrics = @{ Server = @{ StartTime = [datetime]::UtcNow } } RunspacePools = @{} - } } + } + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} +} Describe 'Start-PodeInternalServer' { BeforeAll { @@ -20,7 +23,7 @@ Describe 'Start-PodeInternalServer' { Mock Invoke-PodeScriptBlock { } Mock New-PodeRunspaceState { } Mock New-PodeRunspacePool { } - Mock Start-PodeLoggingRunspace { } + Mock Start-PodeLoggerDispatcher { } Mock Start-PodeTimerRunspace { } Mock Start-PodeScheduleRunspace { } Mock Start-PodeGuiRunspace { } @@ -106,6 +109,7 @@ Describe 'Restart-PodeInternalServer' { Mock Write-PodeErrorLog { } Mock Close-PodeDisposable { } Mock Invoke-PodeEvent { } + Mock Clear-PodeLogging { } } It 'Resetting the server values' { $PodeContext = @{ @@ -125,7 +129,9 @@ Describe 'Restart-PodeInternalServer' { key = @{} } Logging = @{ - Types = @{ 'key' = 'value' } + Type = @{ 'key' = 'value' } + LogsToProcess = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + Method = @{ 'key' = 'value' } } Middleware = @{ 'key' = 'value' } Endpoints = @{ 'key' = 'value' } @@ -243,7 +249,7 @@ Describe 'Restart-PodeInternalServer' { Restart-PodeInternalServer | Out-Null $PodeContext.Server.Routes['GET'].Count | Should -Be 0 - $PodeContext.Server.Logging.Types.Count | Should -Be 0 + $PodeContext.Server.Logging.Type.Count | Should -Be 0 $PodeContext.Server.Middleware.Count | Should -Be 0 $PodeContext.Server.Endware.Count | Should -Be 0 $PodeContext.Server.Sessions.Count | Should -Be 0 diff --git a/tests/unit/Serverless.Tests.ps1 b/tests/unit/Serverless.Tests.ps1 index 325bc3336..5b26f734a 100644 --- a/tests/unit/Serverless.Tests.ps1 +++ b/tests/unit/Serverless.Tests.ps1 @@ -6,6 +6,10 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} + Mock Test-PodeLoggerEnabled { return $false } } Describe 'Start-PodeAzFuncServer' { BeforeAll { diff --git a/tests/unit/Sessions.Tests.ps1 b/tests/unit/Sessions.Tests.ps1 index dcf9a77b6..c27bece15 100644 --- a/tests/unit/Sessions.Tests.ps1 +++ b/tests/unit/Sessions.Tests.ps1 @@ -7,6 +7,9 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} + $now = [datetime]::UtcNow } diff --git a/tests/unit/State.Tests.ps1 b/tests/unit/State.Tests.ps1 index 2f3125570..2f9196d66 100644 --- a/tests/unit/State.Tests.ps1 +++ b/tests/unit/State.Tests.ps1 @@ -7,6 +7,9 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} + $PodeContext = @{ 'Server' = $null; } } diff --git a/tests/unit/Timers.Tests.ps1 b/tests/unit/Timers.Tests.ps1 index 8ea420d62..9019585d1 100644 --- a/tests/unit/Timers.Tests.ps1 +++ b/tests/unit/Timers.Tests.ps1 @@ -8,6 +8,9 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' + # Mock Write-PodeTraceLog to avoid load Pode C# component + Mock Write-PodeTraceLog {} + $PodeContext = @{ 'Server' = $null; } } From d7fa9f7a7c3a821aebca9ec0814131b6316f6df2 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 21 Sep 2024 14:29:53 -0700 Subject: [PATCH 02/53] fix tests --- src/Private/Runspaces.ps1 | 1 - tests/integration/Schedules.Tests.ps1 | 47 +++++++++------------------ 2 files changed, 15 insertions(+), 33 deletions(-) diff --git a/src/Private/Runspaces.ps1 b/src/Private/Runspaces.ps1 index 7bedb2f33..336e1b821 100644 --- a/src/Private/Runspaces.ps1 +++ b/src/Private/Runspaces.ps1 @@ -40,7 +40,6 @@ function Add-PodeRunspace { param( [Parameter(Mandatory = $true)] - [ValidateSet('Main', 'Signals', 'Schedules', 'Gui', 'Web', 'Smtp', 'Tcp', 'Tasks', 'WebSockets', 'Files', 'Timers')] [string] $Type, diff --git a/tests/integration/Schedules.Tests.ps1 b/tests/integration/Schedules.Tests.ps1 index 74a53bd14..ab4e1f898 100644 --- a/tests/integration/Schedules.Tests.ps1 +++ b/tests/integration/Schedules.Tests.ps1 @@ -23,16 +23,14 @@ Describe 'Schedules' { Set-PodeState -Name 'test3' -Value @{eventList = @() } - Add-PodeSchedule -Name 'TestEvents' -Cron '* * * * *' -Limit 2 -ScriptBlock { - param($Event, $Message1, $Message2) + Add-PodeSchedule -Name 'TestEvents' -Cron '* * * * *' -Limit 2 -OnStart -ScriptBlock { + param($Event ) Lock-PodeObject -ScriptBlock { $test3 = (Get-PodeState -Name 'test3') $test3.eventList += @{ - message = 'Hello, world!' - 'Last' = $Event.Sender.LastTriggerTime - 'Next' = $Event.Sender.NextTriggerTime - 'Message1' = $Message1 - 'Message2' = $Message2 + message = 'Hello, world!' + 'Last' = $Event.Sender.LastTriggerTime + 'Next' = $Event.Sender.NextTriggerTime } } } @@ -50,16 +48,6 @@ Describe 'Schedules' { } } - - # adhoc invoke a schedule's logic - Add-PodeRoute -Method Post -Path '/eventlist/run' -ScriptBlock { - Invoke-PodeSchedule -Name 'TestEvents' -ArgumentList @{ - Message1 = 'Hello!' - Message2 = 'Bye!' - } - Write-PodeJsonResponse -Value ( @{Result = 'ok' } ) - } - # test1 Set-PodeState -Name 'Test1' -Value 0 Add-PodeSchedule -Name 'Test1' -Cron '* * * * *' -ScriptBlock { @@ -95,10 +83,6 @@ Describe 'Schedules' { Get-Job -Name 'Pode' | Remove-Job -Force } - It 'Invoke schedule events' { - $result = Invoke-RestMethod -Uri "$($Endpoint)/eventlist/run" -Method post - $result.Result | Should -Be 'OK' - } It 'Schedule updates state value - full cron' { $result = Invoke-RestMethod -Uri "$($Endpoint)/test1" -Method Get $result.Result | Should -Be 1337 @@ -122,17 +106,16 @@ Describe 'Schedules' { $result.Count | Should -Be 2 $result.eventList.GetType() | Should -Be 'System.Object[]' $result.eventList.Count | Should -Be 2 - $result.eventList[0].Message1 | Should -Be 'Hello!' - $result.eventList[0].Message2 | Should -Be 'Bye!' - $result.eventList[0].Message | Should -Be 'Hello, world!' - $result.eventList[0].Last | Should -BeNullOrEmpty - $result.eventList[0].next | Should -not -BeNullOrEmpty - - $result.eventList[1].Message1 | Should -BeNullOrEmpty - $result.eventList[1].Message2 | Should -BeNullOrEmpty - $result.eventList[1].Message | Should -Be 'Hello, world!' - $result.eventList[1].Last | Should -not -BeNullOrEmpty - $result.eventList[1].next | Should -not -BeNullOrEmpty + + + if ( $null -eq $result.eventList[0].Next ) { $index = 0 } else { $index = 1 } + $result.eventList[$index].Message | Should -Be 'Hello, world!' + $result.eventList[$index].Last | Should -not -BeNullOrEmpty + $result.eventList[$index].next | Should -BeNullOrEmpty + if ($index -eq 0) { $index = 1 }else { $index = 0 } + $result.eventList[$index].Message | Should -Be 'Hello, world!' + $result.eventList[$index].Last | Should -not -BeNullOrEmpty + $result.eventList[$index].next | Should -not -BeNullOrEmpty } } \ No newline at end of file From 811cbd628dd96aabedb3272d3669b27d65532594 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sun, 22 Sep 2024 08:24:09 -0700 Subject: [PATCH 03/53] modified: src/Private/Context.ps1 --- src/Private/Context.ps1 | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Private/Context.ps1 b/src/Private/Context.ps1 index bd14984ce..523f06e9d 100644 --- a/src/Private/Context.ps1 +++ b/src/Private/Context.ps1 @@ -126,7 +126,6 @@ function New-PodeContext { Add-Member -MemberType NoteProperty -Name Runspaces -Value $null -PassThru | Add-Member -MemberType NoteProperty -Name RunspaceState -Value $null -PassThru | Add-Member -MemberType NoteProperty -Name Tokens -Value @{} -PassThru | - Add-Member -MemberType NoteProperty -Name LogsToProcess -Value $null -PassThru | Add-Member -MemberType NoteProperty -Name Threading -Value @{} -PassThru | Add-Member -MemberType NoteProperty -Name Server -Value @{} -PassThru | Add-Member -MemberType NoteProperty -Name Metrics -Value @{} -PassThru | @@ -853,7 +852,6 @@ function New-PodeStateContext { Add-Member -MemberType NoteProperty -Name RunspacePools -Value $Context.RunspacePools -PassThru | Add-Member -MemberType NoteProperty -Name Tokens -Value $Context.Tokens -PassThru | Add-Member -MemberType NoteProperty -Name Metrics -Value $Context.Metrics -PassThru | - Add-Member -MemberType NoteProperty -Name LogsToProcess -Value $Context.LogsToProcess -PassThru | Add-Member -MemberType NoteProperty -Name Threading -Value $Context.Threading -PassThru | Add-Member -MemberType NoteProperty -Name Server -Value $Context.Server -PassThru) } From 60f8601307ff9a81e4a27d250b597299ca9b23ca Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sun, 22 Sep 2024 08:44:12 -0700 Subject: [PATCH 04/53] fix en_us language --- PSScriptAnalyzerSettings.psd1 | 4 +++- src/Locales/en-us/Pode.psd1 | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/PSScriptAnalyzerSettings.psd1 b/PSScriptAnalyzerSettings.psd1 index 5ae2be002..21e8d8a5e 100644 --- a/PSScriptAnalyzerSettings.psd1 +++ b/PSScriptAnalyzerSettings.psd1 @@ -24,6 +24,8 @@ 'PSAvoidUsingUsernameAndPasswordParams', 'PSUseProcessBlockForPipelineCommand', 'PSAvoidUsingConvertToSecureStringWithPlainText', - 'PSReviewUnusedParameter' + 'PSReviewUnusedParameter', + 'PSAvoidAssignmentToAutomaticVariable' ) + } \ No newline at end of file diff --git a/src/Locales/en-us/Pode.psd1 b/src/Locales/en-us/Pode.psd1 index 63c32a969..401dcd0d7 100644 --- a/src/Locales/en-us/Pode.psd1 +++ b/src/Locales/en-us/Pode.psd1 @@ -153,7 +153,6 @@ parameterNotSuppliedInRequestExceptionMessage = "A parameter called '{0}' was not supplied in the request or has no data available." cacheStorageNotFoundForSetExceptionMessage = "Cache storage with name '{0}' not found when attempting to set cached item '{1}'" methodPathAlreadyDefinedExceptionMessage = '[{0}] {1}: Already defined.' - errorLoggingAlreadyEnabledExceptionMessage = 'Error Logging has already been enabled.' valueForUsingVariableNotFoundExceptionMessage = "Value for '`$using:{0}' could not be found." rapidPdfDoesNotSupportOpenApi31ExceptionMessage = "The Document tool RapidPdf doesn't support OpenAPI 3.1" oauth2ClientSecretRequiredExceptionMessage = 'OAuth2 requires a Client Secret when not using PKCE.' @@ -281,10 +280,13 @@ invalidSchemeForAuthValidatorExceptionMessage = "The supplied '{0}' Scheme for the '{1}' authentication validator requires a valid ScriptBlock." sseFailedToBroadcastExceptionMessage = 'SSE failed to broadcast due to defined SSE broadcast level for {0}: {1}' adModuleWindowsOnlyExceptionMessage = 'Active Directory module only available on Windows OS.' - requestLoggingAlreadyEnabledExceptionMessage = 'Request Logging has already been enabled.' invalidAccessControlMaxAgeDurationExceptionMessage = 'Invalid Access-Control-Max-Age duration supplied: {0}. Should be greater than 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'OpenAPI definition named {0} already exists.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag cannot be used inside a Select-PodeOADefinition 'ScriptBlock'." + fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "The function '{0}' does not accept an array as pipeline input." + loggingAlreadyEnabledExceptionMessage = "Logging '{0}' has already been enabled." + invalidEncodingExceptionMessage = 'Invalid encoding: {0}' + syslogProtocolExceptionMessage = 'The Syslog protocol can use only RFC3164 or RFC5424.' taskProcessDoesNotExistExceptionMessage = 'Task process does not exist: {0}' scheduleProcessDoesNotExistExceptionMessage = 'Schedule process does not exist: {0}' definitionTagChangeNotAllowedExceptionMessage = 'Definition Tag for a Route cannot be changed.' From c284d8c1a88e071f24cb4c3f2e47141898e3ad62 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Mon, 23 Sep 2024 08:23:59 -0700 Subject: [PATCH 05/53] Fix runspace naming --- src/Private/Logging.ps1 | 15 ++------------- src/Public/Logging.ps1 | 2 -- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index 97f0a2c17..3dd76bd40 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -20,8 +20,6 @@ function Get-PodeLoggingTerminalMethod { return { param($MethodId) - ([System.Management.Automation.Runspaces.Runspace]::DefaultRunspace).Name = "LoggingTerminalMethod_$MethodId" - if ($PodeContext.Server.Quiet) { return } @@ -67,8 +65,6 @@ function Get-PodeLoggingFileMethod { return { param($MethodId) - # Set the name of the default runspace - ([System.Management.Automation.Runspaces.Runspace]::DefaultRunspace).Name = "LoggingFileMethod_$MethodId" $log = @{} while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { Start-Sleep -Milliseconds 100 @@ -297,8 +293,6 @@ function Get-PodeLoggingSysLogMethod { return $value } - # Set the name of the default runspace - ([System.Management.Automation.Runspaces.Runspace]::DefaultRunspace).Name = "LoggingSysLogMethod_$MethodId" $log = @{} $socketCreated = $false try { @@ -436,8 +430,6 @@ function Get-PodeLoggingRestfulMethod { return { param($MethodId) - # Set the name of the default runspace - ([System.Management.Automation.Runspaces.Runspace]::DefaultRunspace).Name = "LoggingRestfulMethod_$MethodId" $log = @{} while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { Start-Sleep -Milliseconds 100 @@ -558,8 +550,6 @@ function Get-PodeLoggingEventViewerMethod { return { param($MethodId) - # Set the name of the default runspace - ([System.Management.Automation.Runspaces.Runspace]::DefaultRunspace).Name = "LoggingEventViewerMethod_$MethodId" $log = @{} while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { Start-Sleep -Milliseconds 100 @@ -1138,7 +1128,6 @@ function Start-PodeLoggerDispatcher { } $scriptBlock = { - ([System.Management.Automation.Runspaces.Runspace]::DefaultRunspace).Name = 'LoggerDispatcher' $log = @{} # Wait for the server to start before processing logs @@ -1249,14 +1238,14 @@ function Start-PodeLoggerDispatcher { foreach ($methodId in $uniqueMethodIds) { if ($null -ne $PodeContext.Server.Logging.Method[$methodId]) { $PodeContext.Server.Logging.Method[$methodId].Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() - $PodeContext.Server.Logging.Method[$methodId].Runspace = Add-PodeRunspace -PassThru -Type Logs -ScriptBlock $PodeContext.Server.Logging.Method[$methodId].ScriptBlock -Parameters @{ MethodId = $methodId } + $PodeContext.Server.Logging.Method[$methodId].Runspace = Add-PodeRunspace -PassThru -Type Logs -ScriptBlock $PodeContext.Server.Logging.Method[$methodId].ScriptBlock -Parameters @{ MethodId = $methodId } -Name 'Method' -Id $methodId | Out-Null } } } } # Add the logger dispatcher runspace - Add-PodeRunspace -Type Logs -ScriptBlock $scriptBlock + Add-PodeRunspace -Type Logs -ScriptBlock $scriptBlock -Name 'Dispatcher' } <# diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 33e21de17..836f5962b 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -514,8 +514,6 @@ function New-PodeLoggingMethod { $enanchedScriptBlock = { param($MethodId) - ([System.Management.Automation.Runspaces.Runspace]::DefaultRunspace).Name = "LoggingCustomMethod_$MethodId" - $log = @{} while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { Start-Sleep -Milliseconds 100 From 984822eabbf475cd2fc9b699722a50152886b573 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 28 Sep 2024 09:03:17 -0700 Subject: [PATCH 06/53] fix Locales duplicated key --- src/Locales/ar/Pode.psd1 | 1 - src/Locales/de/Pode.psd1 | 1 - src/Locales/en-us/Pode.psd1 | 1 - src/Locales/en/Pode.psd1 | 1 - src/Locales/es/Pode.psd1 | 1 - src/Locales/fr/Pode.psd1 | 1 - src/Locales/it/Pode.psd1 | 1 - src/Locales/ja/Pode.psd1 | 1 - src/Locales/ko/Pode.psd1 | 1 - src/Locales/nl/Pode.psd1 | 1 - src/Locales/pl/Pode.psd1 | 1 - src/Locales/pt/Pode.psd1 | 1 - src/Locales/zh/Pode.psd1 | 1 - 13 files changed, 13 deletions(-) diff --git a/src/Locales/ar/Pode.psd1 b/src/Locales/ar/Pode.psd1 index ce568f173..9c5b8ffe2 100644 --- a/src/Locales/ar/Pode.psd1 +++ b/src/Locales/ar/Pode.psd1 @@ -283,7 +283,6 @@ invalidAccessControlMaxAgeDurationExceptionMessage = 'مدة Access-Control-Max-Age غير صالحة المقدمة: {0}. يجب أن تكون أكبر من 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'تعريف OpenAPI باسم {0} موجود بالفعل.' renamePodeOADefinitionTagExceptionMessage = "لا يمكن استخدام Rename-PodeOADefinitionTag داخل Select-PodeOADefinition 'ScriptBlock'." - fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "الدالة '{0}' لا تقبل مصفوفة كمدخل لأنبوب البيانات." loggingAlreadyEnabledExceptionMessage = "تم تمكين تسجيل '{0}' بالفعل." invalidEncodingExceptionMessage = 'ترميز غير صالح: {0}' syslogProtocolExceptionMessage = 'يمكن لبروتوكول Syslog استخدام RFC3164 أو RFC5424 فقط.' diff --git a/src/Locales/de/Pode.psd1 b/src/Locales/de/Pode.psd1 index 71966df2e..bc7524fcf 100644 --- a/src/Locales/de/Pode.psd1 +++ b/src/Locales/de/Pode.psd1 @@ -283,7 +283,6 @@ invalidAccessControlMaxAgeDurationExceptionMessage = 'Ungültige Access-Control-Max-Age-Dauer angegeben: {0}. Sollte größer als 0 sein.' openApiDefinitionAlreadyExistsExceptionMessage = 'Die OpenAPI-Definition mit dem Namen {0} existiert bereits.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag kann nicht innerhalb eines 'ScriptBlock' von Select-PodeOADefinition verwendet werden." - fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "Die Funktion '{0}' akzeptiert kein Array als Pipeline-Eingabe." loggingAlreadyEnabledExceptionMessage = "Das Logging '{0}' wurde bereits aktiviert." invalidEncodingExceptionMessage = 'Ungültige Codierung: {0}' syslogProtocolExceptionMessage = 'Das Syslog-Protokoll kann nur RFC3164 oder RFC5424 verwenden.' diff --git a/src/Locales/en-us/Pode.psd1 b/src/Locales/en-us/Pode.psd1 index 737e9a3f4..8abd59892 100644 --- a/src/Locales/en-us/Pode.psd1 +++ b/src/Locales/en-us/Pode.psd1 @@ -283,7 +283,6 @@ invalidAccessControlMaxAgeDurationExceptionMessage = 'Invalid Access-Control-Max-Age duration supplied: {0}. Should be greater than 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'OpenAPI definition named {0} already exists.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag cannot be used inside a Select-PodeOADefinition 'ScriptBlock'." - fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "The function '{0}' does not accept an array as pipeline input." loggingAlreadyEnabledExceptionMessage = "Logging '{0}' has already been enabled." invalidEncodingExceptionMessage = 'Invalid encoding: {0}' syslogProtocolExceptionMessage = 'The Syslog protocol can use only RFC3164 or RFC5424.' diff --git a/src/Locales/en/Pode.psd1 b/src/Locales/en/Pode.psd1 index 8db0b8cbd..a307fcafa 100644 --- a/src/Locales/en/Pode.psd1 +++ b/src/Locales/en/Pode.psd1 @@ -283,7 +283,6 @@ invalidAccessControlMaxAgeDurationExceptionMessage = 'Invalid Access-Control-Max-Age duration supplied: {0}. Should be greater than 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'OpenAPI definition named {0} already exists.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag cannot be used inside a Select-PodeOADefinition 'ScriptBlock'." - fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "The function '{0}' does not accept an array as pipeline input." loggingAlreadyEnabledExceptionMessage = "Logging '{0}' has already been enabled." invalidEncodingExceptionMessage = 'Invalid encoding: {0}' syslogProtocolExceptionMessage = 'The Syslog protocol can use only RFC3164 or RFC5424.' diff --git a/src/Locales/es/Pode.psd1 b/src/Locales/es/Pode.psd1 index 5fdcc58f8..ac5bae926 100644 --- a/src/Locales/es/Pode.psd1 +++ b/src/Locales/es/Pode.psd1 @@ -283,7 +283,6 @@ invalidAccessControlMaxAgeDurationExceptionMessage = 'Duración inválida para Access-Control-Max-Age proporcionada: {0}. Debe ser mayor que 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'La definición de OpenAPI con el nombre {0} ya existe.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag no se puede usar dentro de un 'ScriptBlock' de Select-PodeOADefinition." - fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "La función '{0}' no acepta una matriz como entrada de canalización." loggingAlreadyEnabledExceptionMessage = "El registro '{0}' ya ha sido habilitado." invalidEncodingExceptionMessage = 'Codificación inválida: {0}' syslogProtocolExceptionMessage = 'El protocolo Syslog solo puede usar RFC3164 o RFC5424.' diff --git a/src/Locales/fr/Pode.psd1 b/src/Locales/fr/Pode.psd1 index a21dd1971..26d7107fa 100644 --- a/src/Locales/fr/Pode.psd1 +++ b/src/Locales/fr/Pode.psd1 @@ -283,7 +283,6 @@ invalidAccessControlMaxAgeDurationExceptionMessage = 'Durée Access-Control-Max-Age invalide fournie : {0}. Doit être supérieure à 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'La définition OpenAPI nommée {0} existe déjà.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag ne peut pas être utilisé à l'intérieur d'un 'ScriptBlock' de Select-PodeOADefinition." - fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "La fonction '{0}' n'accepte pas un tableau en tant qu'entrée de pipeline." loggingAlreadyEnabledExceptionMessage = "La journalisation '{0}' a déjà été activée." invalidEncodingExceptionMessage = 'Encodage invalide : {0}' syslogProtocolExceptionMessage = 'Le protocole Syslog ne peut utiliser que RFC3164 ou RFC5424.' diff --git a/src/Locales/it/Pode.psd1 b/src/Locales/it/Pode.psd1 index 663b7281c..b5f1c1f38 100644 --- a/src/Locales/it/Pode.psd1 +++ b/src/Locales/it/Pode.psd1 @@ -283,7 +283,6 @@ invalidAccessControlMaxAgeDurationExceptionMessage = 'Durata non valida fornita per Access-Control-Max-Age: {0}. Deve essere maggiore di 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'La definizione OpenAPI denominata {0} esiste già.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag non può essere utilizzato all'interno di un 'ScriptBlock' di Select-PodeOADefinition." - fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "La funzione '{0}' non accetta una matrice come input della pipeline." loggingAlreadyEnabledExceptionMessage = "Il logging '{0}' è già stato abilitato." invalidEncodingExceptionMessage = 'Codifica non valida: {0}' syslogProtocolExceptionMessage = 'Il protocollo Syslog può utilizzare solo RFC3164 o RFC5424.' diff --git a/src/Locales/ja/Pode.psd1 b/src/Locales/ja/Pode.psd1 index 83e47fe4b..d87e2f9f1 100644 --- a/src/Locales/ja/Pode.psd1 +++ b/src/Locales/ja/Pode.psd1 @@ -283,7 +283,6 @@ invalidAccessControlMaxAgeDurationExceptionMessage = '無効な Access-Control-Max-Age 期間が提供されました:{0}。0 より大きくする必要があります。' openApiDefinitionAlreadyExistsExceptionMessage = '名前が {0} の OpenAPI 定義は既に存在します。' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag は Select-PodeOADefinition 'ScriptBlock' 内で使用できません。" - fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "関数 '{0}' は配列をパイプライン入力として受け付けません。" loggingAlreadyEnabledExceptionMessage = "ログ記録 '{0}' は既に有効になっています。" invalidEncodingExceptionMessage = '無効なエンコーディング: {0}' syslogProtocolExceptionMessage = 'SyslogプロトコルはRFC3164またはRFC5424のみを使用できます。' diff --git a/src/Locales/ko/Pode.psd1 b/src/Locales/ko/Pode.psd1 index 72ca660f6..b2babcb7c 100644 --- a/src/Locales/ko/Pode.psd1 +++ b/src/Locales/ko/Pode.psd1 @@ -283,7 +283,6 @@ invalidAccessControlMaxAgeDurationExceptionMessage = '잘못된 Access-Control-Max-Age 기간이 제공되었습니다: {0}. 0보다 커야 합니다.' openApiDefinitionAlreadyExistsExceptionMessage = '이름이 {0}인 OpenAPI 정의가 이미 존재합니다.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag은 Select-PodeOADefinition 'ScriptBlock' 내에서 사용할 수 없습니다." - fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "함수 '{0}'은(는) 배열을 파이프라인 입력으로 받지 않습니다." loggingAlreadyEnabledExceptionMessage = "로그 '{0}'이(가) 이미 활성화되었습니다." invalidEncodingExceptionMessage = '잘못된 인코딩: {0}' syslogProtocolExceptionMessage = 'Syslog 프로토콜은 RFC3164 또는 RFC5424만 사용할 수 있습니다.' diff --git a/src/Locales/nl/Pode.psd1 b/src/Locales/nl/Pode.psd1 index 39b0bea74..2cf45b83d 100644 --- a/src/Locales/nl/Pode.psd1 +++ b/src/Locales/nl/Pode.psd1 @@ -283,7 +283,6 @@ invalidAccessControlMaxAgeDurationExceptionMessage = 'Ongeldige Access-Control-Max-Age duur opgegeven: {0}. Moet groter zijn dan 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'OpenAPI-definitie met de naam {0} bestaat al.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag kan niet worden gebruikt binnen een Select-PodeOADefinition 'ScriptBlock'." - fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "De functie '{0}' accepteert geen array als pipeline-invoer." loggingAlreadyEnabledExceptionMessage = "Logging '{0}' is al ingeschakeld." invalidEncodingExceptionMessage = 'Ongeldige codering: {0}' syslogProtocolExceptionMessage = 'Het Syslog-protocol kan alleen RFC3164 of RFC5424 gebruiken.' diff --git a/src/Locales/pl/Pode.psd1 b/src/Locales/pl/Pode.psd1 index f4790e523..dd3c01d0d 100644 --- a/src/Locales/pl/Pode.psd1 +++ b/src/Locales/pl/Pode.psd1 @@ -283,7 +283,6 @@ invalidAccessControlMaxAgeDurationExceptionMessage = 'Podano nieprawidłowy czas trwania Access-Control-Max-Age: {0}. Powinien być większy niż 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'Definicja OpenAPI o nazwie {0} już istnieje.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag nie może być używany wewnątrz 'ScriptBlock' Select-PodeOADefinition." - fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "Funkcja '{0}' nie akceptuje tablicy jako wejścia potoku." loggingAlreadyEnabledExceptionMessage = "Rejestrowanie '{0}' jest już włączone." invalidEncodingExceptionMessage = 'Nieprawidłowe kodowanie: {0}' syslogProtocolExceptionMessage = 'Protokół Syslog może używać tylko RFC3164 lub RFC5424.' diff --git a/src/Locales/pt/Pode.psd1 b/src/Locales/pt/Pode.psd1 index 7f6fe766e..5120cea24 100644 --- a/src/Locales/pt/Pode.psd1 +++ b/src/Locales/pt/Pode.psd1 @@ -283,7 +283,6 @@ invalidAccessControlMaxAgeDurationExceptionMessage = 'Duração inválida fornecida para Access-Control-Max-Age: {0}. Deve ser maior que 0.' openApiDefinitionAlreadyExistsExceptionMessage = 'A definição OpenAPI com o nome {0} já existe.' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag não pode ser usado dentro de um 'ScriptBlock' Select-PodeOADefinition." - fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "A função '{0}' não aceita uma matriz como entrada de pipeline." loggingAlreadyEnabledExceptionMessage = "O registro '{0}' já foi habilitado." invalidEncodingExceptionMessage = 'Codificação inválida: {0}' syslogProtocolExceptionMessage = 'O protocolo Syslog só pode usar RFC3164 ou RFC5424.' diff --git a/src/Locales/zh/Pode.psd1 b/src/Locales/zh/Pode.psd1 index f7360664e..97de6bbb1 100644 --- a/src/Locales/zh/Pode.psd1 +++ b/src/Locales/zh/Pode.psd1 @@ -283,7 +283,6 @@ invalidAccessControlMaxAgeDurationExceptionMessage = '提供的 Access-Control-Max-Age 时长无效:{0}。应大于 0。' openApiDefinitionAlreadyExistsExceptionMessage = '名为 {0} 的 OpenAPI 定义已存在。' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag 不能在 Select-PodeOADefinition 'ScriptBlock' 内使用。" - fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "函数 '{0}' 不接受数组作为管道输入。" loggingAlreadyEnabledExceptionMessage = "日志记录 '{0}' 已启用。" invalidEncodingExceptionMessage = '无效的编码: {0}' syslogProtocolExceptionMessage = 'Syslog 协议只能使用 RFC3164 或 RFC5424。' From b0720ccc1305a413e9ecf93c52145f4efb18b6bc Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 28 Sep 2024 11:00:14 -0700 Subject: [PATCH 07/53] Update Logging.ps1 --- src/Public/Logging.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index e54b22d48..836f5962b 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -1131,8 +1131,7 @@ Clears all Logging methods that have been configured. .EXAMPLE Clear-PodeLogger #> -function Clear-PodeLoggers { - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')] +function Clear-PodeLogger { [CmdletBinding()] param() From d16456f865bce5ce0910047d5d99cac85fa8f662 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 19 Oct 2024 13:24:32 -0700 Subject: [PATCH 08/53] minor changes --- docs/Tutorials/Logging/Types/General.md | 10 +- examples/Logging.ps1 | 4 +- src/Pode.psd1 | 4 +- src/Public/Logging.ps1 | 124 ++++++++++++++++-------- 4 files changed, 91 insertions(+), 51 deletions(-) diff --git a/docs/Tutorials/Logging/Types/General.md b/docs/Tutorials/Logging/Types/General.md index 62625e5f1..2db0505fa 100644 --- a/docs/Tutorials/Logging/Types/General.md +++ b/docs/Tutorials/Logging/Types/General.md @@ -3,11 +3,11 @@ Pode supports general logging, allowing you to define custom logging methods and log levels. This feature enables you to write logs based on specified methods, ensuring flexibility and control over logging outputs. -To enable general logging, use the `Enable-PodeGeneralLogging` function. This function takes a hashtable defining the logging method, including a ScriptBlock for log output. You can specify various log levels to be enabled, such as Error, Emergency, Alert, Critical, Warning, Notice, Informational, Info, Verbose, and Debug. +To enable general logging, use the `Enable-PodeCommonLogging` function. This function takes a hashtable defining the logging method, including a ScriptBlock for log output. You can specify various log levels to be enabled, such as Error, Emergency, Alert, Critical, Warning, Notice, Informational, Info, Verbose, and Debug. ## Enabling General Logging -To enable general logging, use the `Enable-PodeGeneralLogging` function, supplying the necessary parameters: +To enable general logging, use the `Enable-PodeCommonLogging` function, supplying the necessary parameters: - `Method`: The hashtable defining the logging method, including the ScriptBlock for log output. - `Levels`: An array of log levels to be enabled for the logging method (default includes Error, Emergency, Alert, Critical, Warning, Notice, Informational, Info, Verbose, Debug). @@ -18,17 +18,17 @@ To enable general logging, use the `Enable-PodeGeneralLogging` function, supplyi ```powershell $method = New-PodeLoggingMethod -syslog -Server 127.0.0.1 -Transport UDP -$method | Enable-PodeGeneralLogging -Name "mysyslog" +$method | Enable-PodeCommonLogging -Name "mysyslog" ``` ## Disabling General Logging -To disable a general logging method, use the `Disable-PodeGeneralLogging` function with the `Name` parameter: +To disable a general logging method, use the `Disable-PodeCommonLogging` function with the `Name` parameter: ### Example ```powershell -Disable-PodeGeneralLogging -Name 'mysyslog' +Disable-PodeCommonLogging -Name 'mysyslog' ``` With these functions, Pode ensures robust and customizable logging capabilities, allowing you to manage logs effectively based on your specific requirements. diff --git a/examples/Logging.ps1 b/examples/Logging.ps1 index 3e426b289..34e87606e 100644 --- a/examples/Logging.ps1 +++ b/examples/Logging.ps1 @@ -60,7 +60,7 @@ Start-PodeServer -browse { } if ( $LoggingType -icontains 'file') { - $logging += New-PodeLoggingMethod -File -Name 'general' -MaxDays 4 -Format Simple -ISO8601 + $logging += New-PodeLoggingMethod -File -Name 'common' -MaxDays 4 -Format Simple -ISO8601 $requestLogging = New-PodeLoggingMethod -File -Name 'requests' -MaxDays 4 } @@ -96,7 +96,7 @@ Start-PodeServer -browse { $logging | Enable-PodeTraceLogging -Raw:$Raw $logging | Enable-PodeErrorLogging -Raw:$Raw -Levels * - $logging | Enable-PodeGeneralLogging -Name 'mylog' -Raw:$Raw + $logging | Enable-PodeCommonLogging -Name 'mylog' -Raw:$Raw Write-PodeLog -Name 'mylog' -Message 'just started' -Level 'Info' # GET request for web page on "localhost:8081/" diff --git a/src/Pode.psd1 b/src/Pode.psd1 index 1d666173a..d7f365bd9 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -285,11 +285,11 @@ 'New-PodeLoggingMethod', 'Enable-PodeRequestLogging', 'Enable-PodeErrorLogging', - 'Enable-PodeGeneralLogging', + 'Enable-PodeCommonLogging', 'Enable-PodeTraceLogging', 'Disable-PodeRequestLogging', 'Disable-PodeErrorLogging', - 'Disable-PodeGeneralLogging', + 'Disable-PodeCommonLogging', 'Disable-PodeTraceLogging', 'Add-PodeLogger', 'Remove-PodeLogger', diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 836f5962b..585d4aa42 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -664,22 +664,6 @@ function Enable-PodeRequestLogging { } } -<# -.SYNOPSIS - Disables Request Logging. - -.DESCRIPTION - Disables Request Logging. - -.EXAMPLE - Disable-PodeRequestLogging -#> -function Disable-PodeRequestLogging { - [CmdletBinding()] - param() - - Remove-PodeLogger -Name (Get-PodeRequestLoggingName) -} <# .SYNOPSIS @@ -784,9 +768,9 @@ function Enable-PodeErrorLogging { .EXAMPLE $method = New-PodeLoggingMethod -syslog -Server 127.0.0.1 -Transport UDP - $method | Enable-PodeGeneralLogging -Name "mysyslog" + $method | Enable-PodeCommonLogging -Name "mysyslog" #> -function Enable-PodeGeneralLogging { +function Enable-PodeCommonLogging { [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] @@ -843,29 +827,6 @@ function Enable-PodeGeneralLogging { } -<# -.SYNOPSIS - Disables a generic logging method in Pode. - -.DESCRIPTION - This function disables a generic logging method in Pode. - -.PARAMETER Name - The name of the logging method to be disable. - -.EXAMPLE - Disable-PodeGeneralLogging -Name 'TestLog' -#> -function Disable-PodeGeneralLogging { - [CmdletBinding()] - param( - [Parameter(Mandatory = $true)] - [string] - $Name) - - Remove-PodeLogger -Name $Name -} - <# .SYNOPSIS @@ -932,6 +893,47 @@ function Enable-PodeTraceLogging { } } +<# +.SYNOPSIS + Disables Request Logging. + +.DESCRIPTION + Disables Request Logging. + +.EXAMPLE + Disable-PodeRequestLogging +#> +function Disable-PodeRequestLogging { + [CmdletBinding()] + param() + + Remove-PodeLogger -Name (Get-PodeRequestLoggingName) +} + +<# +.SYNOPSIS + Disables a generic logging method in Pode. + +.DESCRIPTION + This function disables a generic logging method in Pode. + +.PARAMETER Name + The name of the logging method to be disable. + +.EXAMPLE + Disable-PodeCommonLogging -Name 'TestLog' +#> +function Disable-PodeCommonLogging { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] + $Name) + + Remove-PodeLogger -Name $Name +} + + <# .SYNOPSIS Disables the trace logging method in Pode. @@ -1279,7 +1281,7 @@ function Write-PodeErrorLog { # for exceptions, check the inner exception if ($CheckInnerException -and ($null -ne $Exception.InnerException) -and ![string]::IsNullOrWhiteSpace($Exception.InnerException.Message)) { - $Exception.InnerException | Write-PodeErrorLog + $Exception.InnerException | Write-PodeErrorLog -Level $Level -ThreadId $ThreadId } } } @@ -1313,6 +1315,12 @@ function Write-PodeErrorLog { .PARAMETER ThreadId The ID of the thread where the log entry is generated. +.PARAMETER Exception + An Exception to log. + +.PARAMETER CheckInnerException + If specified, any inner exceptions of the provided exception are also logged. + .EXAMPLE $object | Write-PodeLog -Name 'LogName' @@ -1330,6 +1338,15 @@ function Write-PodeLog { [object] $InputObject, + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Exception')] + [System.Exception] + $Exception, + + [Parameter(ParameterSetName = 'Exception')] + [switch] + $CheckInnerException, + + [Parameter( ParameterSetName = 'inbuilt')] [Parameter( ParameterSetName = 'custom')] [string] $Level = 'Informational', @@ -1355,6 +1372,9 @@ function Write-PodeLog { switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'inbuilt' { + if (!$Level) { + $Level = 'Informational' + } $logItem = @{ Name = $Name Item = $InputObject @@ -1362,6 +1382,9 @@ function Write-PodeLog { break } 'custom' { + if (!$Level) { + $Level = 'Informational' + } $logItem = @{ Name = $Name Item = @{ @@ -1372,6 +1395,20 @@ function Write-PodeLog { } break } + 'exception' { + if (!$Level) { + $Level = 'Error' + } + $logItem = @{ + Name = $Name + Item = @{ + Level = $Level + Message = $Exception.Message + Tag = $Tag + } + } + + } } $log = $PodeContext.Server.Logging.Type[$Name] if ($log.Standard) { @@ -1390,6 +1427,9 @@ function Write-PodeLog { else { $logItem.Item.ThreadId = [System.Threading.Thread]::CurrentThread.ManagedThreadId } + if ($PSCmdlet.ParameterSetName.ToLowerInvariant() -eq 'exception') { + Write-PodeErrorLog -Exception $Exception -Level $Level -CheckInnerException:$CheckInnerException -ThreadId $ThreadId + } } # add the item to be processed [Pode.PodeLogger]::Enqueue($logItem) From ce3ebcf1140b5f7741a8885323b1eb73539f6189 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sun, 20 Oct 2024 09:24:39 -0700 Subject: [PATCH 09/53] fix an PodeContext.cs created by a merge Performance improvement --- examples/Logging.ps1 | 2 +- examples/PetStore/Petstore-OpenApi.ps1 | 13 +- examples/PetStore/server.psd1 | 11 +- src/Listener/PodeContext.cs | 2 +- src/Listener/PodeResponseHeaders.cs | 39 ++++- src/Private/Logging.ps1 | 196 ++++++++++++++++++++----- src/Public/Logging.ps1 | 118 ++++++++------- 7 files changed, 280 insertions(+), 101 deletions(-) diff --git a/examples/Logging.ps1 b/examples/Logging.ps1 index 34e87606e..b76d38c2f 100644 --- a/examples/Logging.ps1 +++ b/examples/Logging.ps1 @@ -60,7 +60,7 @@ Start-PodeServer -browse { } if ( $LoggingType -icontains 'file') { - $logging += New-PodeLoggingMethod -File -Name 'common' -MaxDays 4 -Format Simple -ISO8601 + $logging += New-PodeLoggingMethod -File -Name 'file' -MaxDays 4 -Format Simple -ISO8601 $requestLogging = New-PodeLoggingMethod -File -Name 'requests' -MaxDays 4 } diff --git a/examples/PetStore/Petstore-OpenApi.ps1 b/examples/PetStore/Petstore-OpenApi.ps1 index bf2578bf5..d182f0037 100644 --- a/examples/PetStore/Petstore-OpenApi.ps1 +++ b/examples/PetStore/Petstore-OpenApi.ps1 @@ -92,15 +92,20 @@ Start-PodeServer -Threads 1 -ScriptBlock { Initialize-Pet Initialize-Order Initialize-Users - # attempt to re-initialise the state (will do nothing if the file doesn't exist) + # attempt to re-initialise the state (will do nothing if the file doesn't exist) Restore-PodeState -Path $script:PetDataJson } # Configure Pode server endpoints if ((Get-PodeConfig).Protocol -eq 'Https') { - $Certificate = Join-Path -Path $CertsPath -ChildPath (Get-PodeConfig).Certificate - $CertificateKey = Join-Path -Path $CertsPath -ChildPath (Get-PodeConfig).CertificateKey - Add-PodeEndpoint -Address (Get-PodeConfig).Address -Port (Get-PodeConfig).RestFulPort -Protocol Https -Certificate $Certificate -CertificateKey $CertificateKey -CertificatePassword (Get-PodeConfig).CertificatePassword -Default + if ( (Get-PodeConfig).SelfSigned) { + Add-PodeEndpoint -Address (Get-PodeConfig).Address -Port (Get-PodeConfig).RestFulPort -Protocol Https -Default -SelfSigned + } + else { + $Certificate = Join-Path -Path $CertsPath -ChildPath (Get-PodeConfig).Certificate + $CertificateKey = Join-Path -Path $CertsPath -ChildPath (Get-PodeConfig).CertificateKey + Add-PodeEndpoint -Address (Get-PodeConfig).Address -Port (Get-PodeConfig).RestFulPort -Protocol Https -Certificate $Certificate -CertificateKey $CertificateKey -CertificatePassword (Get-PodeConfig).CertificatePassword -Default + } } else { Add-PodeEndpoint -Address (Get-PodeConfig).Address -Port (Get-PodeConfig).RestFulPort -Protocol Http -Default diff --git a/examples/PetStore/server.psd1 b/examples/PetStore/server.psd1 index 844eed9d3..d1d59e594 100644 --- a/examples/PetStore/server.psd1 +++ b/examples/PetStore/server.psd1 @@ -1,17 +1,18 @@ @{ RestFulPort = 8081 - Protocol = 'Http' + Protocol = 'Https' Address = 'localhost' Certificate = 'Certificate.pem' CertificateKey = 'CertificateKey.key' CertificatePassword = 'password@01' SessionsTtlMinutes = 360 + Selfsigned = $true Server = @{ - Timeout = 60 - BodySize = 100MB + Timeout = 60 + BodySize = 100MB } - Web=@{ - OpenApi=@{ + Web = @{ + OpenApi = @{ DefaultDefinitionTag = 'v3.0.3' } } diff --git a/src/Listener/PodeContext.cs b/src/Listener/PodeContext.cs index d5cf4d9fb..37ac7a0da 100644 --- a/src/Listener/PodeContext.cs +++ b/src/Listener/PodeContext.cs @@ -228,7 +228,7 @@ public async Task Receive() try { PodeLogger.WriteErrorMessage($"Receiving request", Listener, PodeLoggingLevel.Verbose, this); - var close = await Request.Receive(ContextTimeoutToken.Token); + var close = await Request.Receive(ContextTimeoutToken.Token).ConfigureAwait(false); SetContextType(); await EndReceive(close).ConfigureAwait(false); } diff --git a/src/Listener/PodeResponseHeaders.cs b/src/Listener/PodeResponseHeaders.cs index aa2704178..f45eb259b 100644 --- a/src/Listener/PodeResponseHeaders.cs +++ b/src/Listener/PodeResponseHeaders.cs @@ -3,15 +3,29 @@ namespace Pode { + /// + /// Represents a collection of response headers that supports multiple values per header. + /// public class PodeResponseHeaders { + /// + /// Gets or sets the first value of the specified header. + /// + /// The name of the header. public object this[string name] { get => Headers.TryGetValue(name, out IList value) ? value[0] : string.Empty; set => Set(name, value); } + /// + /// Gets the number of headers. + /// public int Count => Headers.Count; + + /// + /// Gets the collection of header names. + /// public ICollection Keys => Headers.Keys; private IDictionary> Headers; @@ -21,16 +35,25 @@ public PodeResponseHeaders() Headers = new Dictionary>(StringComparer.InvariantCultureIgnoreCase); } + /// + /// Determines whether the collection contains the specified header. + /// public bool ContainsKey(string name) { return Headers.ContainsKey(name); } + /// + /// Gets the list of values associated with the specified header. + /// public IList Get(string name) { return Headers.TryGetValue(name, out IList value) ? value : default(IList); } + /// + /// Sets the specified header to the provided value, replacing any existing values. + /// public void Set(string name, object value) { if (!Headers.TryGetValue(name, out var list)) @@ -43,16 +66,23 @@ public void Set(string name, object value) Headers[name].Add(value); } + /// + /// Adds a value to the specified header, preserving any existing values. + /// public void Add(string name, object value) { - if (!Headers.ContainsKey(name)) + if (!Headers.TryGetValue(name, out var list)) { - Headers.Add(name, new List()); + list = new List(); + Headers[name] = list; } - Headers[name].Add(value); + list.Add(value); } + /// + /// Removes the specified header. + /// public void Remove(string name) { if (Headers.ContainsKey(name)) @@ -61,6 +91,9 @@ public void Remove(string name) } } + /// + /// Clears all headers. + /// public void Clear() { Headers.Clear(); diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index 3dd76bd40..8144202fb 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -679,24 +679,111 @@ function Get-PodeLoggingInbuiltType { switch ($options.LogFormat.ToLowerInvariant()) { 'extended' { - return "$($item.Date.ToString('yyyy-MM-dd')) $($item.Date.ToString('HH:mm:ss')) $(sg $item.Host) $(sg $item.User) $(sg $item.Request.Method) $(sg $item.Request.Resource) $(sg $item.Request.Query) $(sg $item.Response.StatusCode) $(sg $item.Response.Size) `"$(sg $item.Request.Agent)`"" + $sb = [System.Text.StringBuilder]::new() + $null = $sb.Append('Date: ') + $null = $sb.Append($item.Date.ToString('yyyy-MM-dd')) + $null = $sb.Append(' ') + $null = $sb.Append($item.Date.ToString('HH:mm:ss')) + $null = $sb.Append(' ') + $null = $sb.Append((sg $item.Host)) + $null = $sb.Append(' ') + $null = $sb.Append((sg $item.User)) + $null = $sb.Append(' ') + $null = $sb.Append((sg $item.Request.Method)) + $null = $sb.Append(' ') + $null = $sb.Append((sg $item.Request.Resource)) + $null = $sb.Append(' ') + $null = $sb.Append((sg $item.Request.Query)) + $null = $sb.Append(' ') + $null = $sb.Append((sg $item.Response.StatusCode)) + $null = $sb.Append(' ') + $null = $sb.Append((sg $item.Response.Size)) + $null = $sb.Append(' "') + $null = $sb.Append((sg $item.Request.Agent)) + $null = $sb.Append('"') + return $sb.ToString() + #return "$($item.Date.ToString('yyyy-MM-dd')) $($item.Date.ToString('HH:mm:ss')) $(sg $item.Host) $(sg $item.User) $(sg $item.Request.Method) $(sg $item.Request.Resource) $(sg $item.Request.Query) $(sg $item.Response.StatusCode) $(sg $item.Response.Size) `"$(sg $item.Request.Agent)`"" } 'common' { + $sb = [System.Text.StringBuilder]::new() + $null = $sb.Append((sg $item.Host)) + $null = $sb.Append(' ') + $null = $sb.Append((sg $item.RfcUserIdentity)) + $null = $sb.Append(' ') + $null = $sb.Append((sg $item.User)) + $null = $sb.Append(' [') + $null = $sb.Append(([regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2'))) + $null = $sb.Append('] "') + $null = $sb.Append((sg $item.Request.Method)) + $null = $sb.Append(' ') + $null = $sb.Append((sg $item.Request.Resource)) + $null = $sb.Append(' ') + $null = $sb.Append((sg $item.Request.Protocol)) + $null = $sb.Append('" ') + $null = $sb.Append((sg $item.Response.StatusCode)) + $null = $sb.Append(' ') + $null = $sb.Append((sg $item.Response.Size)) + return $sb.ToString() # Build the URL with HTTP method - $url = "$(sg $item.Request.Method) $(sg $item.Request.Resource) $(sg $item.Request.Protocol)" - $date = [regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2') - return "$(sg $item.Host) $(sg $item.RfcUserIdentity) $(sg $item.User) [$date] `"$($url)`" $(sg $item.Response.StatusCode) $(sg $item.Response.Size)" + # $url = "$(sg $item.Request.Method) $(sg $item.Request.Resource) $(sg $item.Request.Protocol)" + # $date = [regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2') + # return "$(sg $item.Host) $(sg $item.RfcUserIdentity) $(sg $item.User) [$date] `"$($url)`" $(sg $item.Response.StatusCode) $(sg $item.Response.Size)" } 'json' { - return "{`"time`": `"$($item.Date.ToString('yyyy-MM-ddTHH:mm:ssK'))`",`"remote_ip`": `"$(sg $item.Host)`",`"user`": `"$(sg $item.User)`",`"method`": `"$(sg $item.Request.Method)`",`"uri`": `"$(sg $item.Request.Resource)`",`"query`": `"$(sg $item.Request.Query)`",`"status`": $(sg $item.Response.StatusCode),`"response_size`": $(sg $item.Response.Size),`"user_agent`": `"$(sg $item.Request.Agent)`"}" + $sb = [System.Text.StringBuilder]::new() + $null = $sb.Append('{"time": "') + $null = $sb.Append($item.Date.ToString('yyyy-MM-ddTHH:mm:ssK')) + $null = $sb.Append('","remote_ip": "') + $null = $sb.Append((sg $item.Host)) + $null = $sb.Append('","user": "') + $null = $sb.Append((sg $item.User)) + $null = $sb.Append('","method": "') + $null = $sb.Append((sg $item.Request.Method)) + $null = $sb.Append('","uri": "') + $null = $sb.Append((sg $item.Request.Resource)) + $null = $sb.Append('","query": "') + $null = $sb.Append((sg $item.Request.Query)) + $null = $sb.Append('","status": ') + $null = $sb.Append((sg $item.Response.StatusCode)) + $null = $sb.Append(',"response_size": ') + $null = $sb.Append((sg $item.Response.Size)) + $null = $sb.Append(',"user_agent": "') + $null = $sb.Append((sg $item.Request.Agent)) + $null = $sb.Append('"}') + return $sb.ToString() + # return "{`"time`": `"$($item.Date.ToString('yyyy-MM-ddTHH:mm:ssK'))`",`"remote_ip`": `"$(sg $item.Host)`",`"user`": `"$(sg $item.User)`",`"method`": `"$(sg $item.Request.Method)`",`"uri`": `"$(sg $item.Request.Resource)`",`"query`": `"$(sg $item.Request.Query)`",`"status`": $(sg $item.Response.StatusCode),`"response_size`": $(sg $item.Response.Size),`"user_agent`": `"$(sg $item.Request.Agent)`"}" } # Combined is the default default { + $sb = [System.Text.StringBuilder]::new() + $null = $sb.Append((sg $item.Host)) + $null = $sb.Append(' ') + $null = $sb.Append((sg $item.RfcUserIdentity)) + $null = $sb.Append(' ') + $null = $sb.Append((sg $item.User)) + $null = $sb.Append(' [') + $null = $sb.Append(([regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2'))) + $null = $sb.Append('] "') + $null = $sb.Append((sg $item.Request.Method)) + $null = $sb.Append(' ') + $null = $sb.Append((sg $item.Request.Resource)) + $null = $sb.Append(' ') + $null = $sb.Append((sg $item.Request.Protocol)) + $null = $sb.Append('" ') + $null = $sb.Append((sg $item.Response.StatusCode)) + $null = $sb.Append(' ') + $null = $sb.Append((sg $item.Response.Size)) + $null = $sb.Append(' "') + $null = $sb.Append((sg $item.Request.Referrer)) + $null = $sb.Append('" "') + $null = $sb.Append((sg $item.Request.Agent)) + $null = $sb.Append('"') + return $sb.ToString() # Build the URL with HTTP method - $url = "$(sg $item.Request.Method) $(sg $item.Request.Resource) $(sg $item.Request.Protocol)" - $date = [regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2') + # $url = "$(sg $item.Request.Method) $(sg $item.Request.Resource) $(sg $item.Request.Protocol)" + # $date = [regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2') # Build and return the request row - return "$(sg $item.Host) $(sg $item.RfcUserIdentity) $(sg $item.User) [$date] `"$($url)`" $(sg $item.Response.StatusCode) $(sg $item.Response.Size) `"$(sg $item.Request.Referrer)`" `"$(sg $item.Request.Agent)`"" + # return "$(sg $item.Host) $(sg $item.RfcUserIdentity) $(sg $item.User) [$date] `"$($url)`" $(sg $item.Response.StatusCode) $(sg $item.Response.Size) `"$(sg $item.Request.Referrer)`" `"$(sg $item.Request.Agent)`"" } } return $item @@ -717,18 +804,28 @@ function Get-PodeLoggingInbuiltType { return $item } - # Build the exception details - $row = @( - "Date: $($item.Date.ToString($options.DataFormat))", - "Level: $($item.Level)", - "ThreadId: $($item.ThreadId)", - "Server: $($item.Server)", - "Category: $($item.Category)", - "Message: $($item.Message)", - "StackTrace: $($item.StackTrace)" - ) - # Join the details and return - return "$($row -join "`n")`n" + + $sb = [System.Text.StringBuilder]::new() + + # Append the details to the StringBuilder + $null = $sb.Append('Date: ') + $null = $sb.AppendLine($item.Date.ToString($options.DataFormat)) + $null = $sb.Append('Level: ') + $null = $sb.AppendLine($item.Level) + $null = $sb.Append('ThreadId: ') + $null = $sb.AppendLine($item.ThreadId) + $null = $sb.Append('Server: ') + $null = $sb.AppendLine($item.Server) + $null = $sb.Append('Category: ') + $null = $sb.AppendLine($item.Category) + $null = $sb.Append('Message: ') + $null = $sb.AppendLine($item.Message) + $null = $sb.Append('StackTrace: ') + $null = $sb.AppendLine($item.StackTrace) + + # Return the built string + return $sb.ToString() + } } 'general' { @@ -744,8 +841,20 @@ function Get-PodeLoggingInbuiltType { if ($options.Raw) { return $item } - - return "[$($item.Date.ToString($options.DataFormat))] $($item.Level) $( $item.Tag) $($item.ThreadId) $($item.Message)" + # Optimized concatenation using Append + $sb = [System.Text.StringBuilder]::new() + $null = $sb.Append('[') + $null = $sb.Append($item.Date.ToString($options.DataFormat)) + $null = $sb.Append('] ') + $null = $sb.Append($item.Level) + $null = $sb.Append(' ') + $null = $sb.Append($item.Tag) + $null = $sb.Append(' ') + $null = $sb.Append($item.ThreadId) + $null = $sb.Append(' ') + $null = $sb.Append($item.Message) + return $sb.ToString() + #return "[$($item.Date.ToString($options.DataFormat))] $($item.Level) $( $item.Tag) $($item.ThreadId) $($item.Message)" } } @@ -757,8 +866,20 @@ function Get-PodeLoggingInbuiltType { if ($options.Raw) { return $item } - - return "[$($item.Date.ToString($options.DataFormat))] $($item.Level) $( $item.Tag) $($item.ThreadId) $($item.Message)" + # Optimized concatenation using Append + $sb = [System.Text.StringBuilder]::new() + $null = $sb.Append('[') + $null = $sb.Append($item.Date.ToString($options.DataFormat)) + $null = $sb.Append('] ') + $null = $sb.Append($item.Level) + $null = $sb.Append(' ') + $null = $sb.Append($item.Tag) + $null = $sb.Append(' ') + $null = $sb.Append($item.ThreadId) + $null = $sb.Append(' ') + $null = $sb.Append($item.Message) + return $sb.ToString() + # return "[$($item.Date.ToString($options.DataFormat))] $($item.Level) $( $item.Tag) $($item.ThreadId) $($item.Message)" } } } @@ -805,20 +926,20 @@ function Get-PodeErrorLoggingName { <# .SYNOPSIS -Gets the name of the main logger. + Gets the name of the main logger. .DESCRIPTION -This function returns the name of the main logger used in Pode. + This function returns the name of the main logger used in Pode. -.RETURNS -[string] - The name of the main logger. +.OUTPUTS + [string] - The name of the main logger. -.EXAMPLE -Get-PodeMainLoggingName + .EXAMPLE + Get-PodeTraceLoggingName #> -function Get-PodeMainLoggingName { +function Get-PodeTraceLoggingName { # Return the name of the main logger - return '__pode_log_main__' + return '__pode_log_trace__' } @@ -1153,11 +1274,16 @@ function Start-PodeLoggerDispatcher { Write-PodeErrorLog -Exception $log.Item -Level = 'Error' -ThreadId $log.Item.ThreadId } else { - Write-PodeLog -Name (Get-PodeErrorLoggingName) -Message $log.Item.Message -Level 'error' -ThreadId $log.Item.ThreadId -Tag 'Listener' - + if ($log.Item.Level -eq [Pode.PodeLoggingLevel]::Error) { + Write-PodeErrorLog -Message $log.Item.Message -ThreadId $log.Item.ThreadId -Tag 'Listener' + } + else { + Write-PodeLog -Name (Get-PodeTraceLoggingName) -Message $log.Item.Message -Level $log.Item.Level -ThreadId $log.Item.ThreadId -Tag 'Listener' + } } continue } + # Run the log item through the appropriate method $logger = $PodeContext.Server.Logging.Type[$log.Name] $now = [datetime]::Now @@ -1319,7 +1445,7 @@ function Write-PodeTraceLog { ) # Do nothing if logging is disabled, or error logging isn't set up - $name = Get-PodeMainLoggingName + $name = Get-PodeTraceLoggingName if (!(Test-PodeLoggerEnabled -Name $name)) { return } diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 585d4aa42..750aef96e 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -857,7 +857,7 @@ function Enable-PodeTraceLogging { ) begin { $pipelineMethods = @() - $name = Get-PodeMainLoggingName + $name = Get-PodeTraceLoggingName # error if it's already enabled if ($PodeContext.Server.Logging.Type.Contains($name)) { throw ($PodeLocale.loggingAlreadyEnabledExceptionMessage -f $name) @@ -928,7 +928,8 @@ function Disable-PodeCommonLogging { param( [Parameter(Mandatory = $true)] [string] - $Name) + $Name + ) Remove-PodeLogger -Name $Name } @@ -948,7 +949,7 @@ function Disable-PodeTraceLogging { [CmdletBinding()] param() - Remove-PodeLogger -Name (Get-PodeMainLoggingName) + Remove-PodeLogger -Name (Get-PodeTraceLoggingName) } <# @@ -1193,11 +1194,11 @@ function Write-PodeErrorLog { [System.Management.Automation.ErrorRecord] $ErrorRecord, - [Parameter(Mandatory = $true, ParameterSetName = 'ErrorMessage')] + [Parameter(Mandatory = $true, ParameterSetName = 'Message')] [string] - $ErrorMessage, + $Message, - [Parameter( ParameterSetName = 'ErrorMessage')] + [Parameter( ParameterSetName = 'Message')] [System.Management.Automation.ErrorCategory] $Category = [System.Management.Automation.ErrorCategory]::NotSpecified, @@ -1246,10 +1247,10 @@ function Write-PodeErrorLog { StackTrace = $ErrorRecord.ScriptStackTrace } } - 'ErrorMessage' { + 'Message' { $item = @{ Category = $Category.ToString() - Message = $ErrorMessage + Message = $Message } } } @@ -1289,52 +1290,63 @@ function Write-PodeErrorLog { <# .SYNOPSIS - Writes an object to a configured custom or built-in logging method. + Writes an object, exception, or custom message to a configured custom or built-in logging method. .DESCRIPTION - This function writes an object to a configured logging method in Pode. - It supports both custom and built-in logging methods, allowing for structured logging with different log levels and messages. + This function writes an object, custom log message, or exception to a logging method in Pode. + It supports both custom and built-in logging methods, allowing structured logging with different log levels, messages, tags, and additional details like thread ID. + The logging method can be used to write errors, warnings, and informational logs in a structured manner, depending on the log level and source of the log. + Optionally, it can suppress reporting of errors to the error log if the same error is logged. .PARAMETER Name - The name of the logging method. + The name of the logging method (e.g., 'Console', 'File', 'Syslog'). .PARAMETER InputObject - The object to write to the logging method. + The object to write to the logging method. This is the default parameter set. .PARAMETER Level - The log level for the custom logging method (Default: 'Informational'). + The log level for the custom logging method (Default: 'Informational'). Log levels include 'Informational', 'Warning', 'Error', etc. .PARAMETER Message - The log message for the custom logging method. + The log message for the custom logging method. Required for custom logging. .PARAMETER Tag A string that identifies the source application, service, or process generating the log message. - The tag helps in distinguishing log messages from different sources and makes it easier to filter and analyze logs. - It is typically a short identifier such as the application name or process ID. + The tag helps distinguish log messages from different sources, making it easier to filter and analyze logs. Default is '-'. .PARAMETER ThreadId - The ID of the thread where the log entry is generated. + The ID of the thread where the log entry is generated. If not specified, the current thread ID will be used. .PARAMETER Exception - An Exception to log. + An exception object to log. Required for the 'Exception' parameter set. .PARAMETER CheckInnerException If specified, any inner exceptions of the provided exception are also logged. +.PARAMETER SuppressErrorLog + A switch to suppress writing the error to the error log if it has already been logged by this function. Useful to prevent duplicate error logging. + .EXAMPLE $object | Write-PodeLog -Name 'LogName' .EXAMPLE Write-PodeLog -Name 'CustomLog' -Level 'Error' -Message 'An error occurred.' + +.EXAMPLE + try { + # Some code that throws an exception + } catch { + Write-PodeLog -Name 'Syslog' -Exception $_ -SuppressErrorLog + } #> function Write-PodeLog { - [CmdletBinding(DefaultParameterSetName = 'inbuilt')] + [CmdletBinding(DefaultParameterSetName = 'custom')] param( [Parameter(Mandatory = $true)] [string] $Name, - [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'inbuilt')] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'InputObject')] [object] $InputObject, @@ -1346,7 +1358,7 @@ function Write-PodeLog { [switch] $CheckInnerException, - [Parameter( ParameterSetName = 'inbuilt')] + [Parameter( ParameterSetName = 'InputObject')] [Parameter( ParameterSetName = 'custom')] [string] $Level = 'Informational', @@ -1361,30 +1373,31 @@ function Write-PodeLog { [Parameter()] [int] - $ThreadId + $ThreadId, + + [Parameter()] + $SuppressErrorLog ) Process { - # do nothing if logging is disabled, or logger isn't setup + # Skip logging if the logger is disabled or not set up. if (!(Test-PodeLoggerEnabled -Name $Name)) { return } + # Define the log item based on the selected parameter set. switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { - 'inbuilt' { - if (!$Level) { - $Level = 'Informational' - } + 'inputobject' { + if (!$Level) { $Level = 'Informational' } $logItem = @{ - Name = $Name - Item = $InputObject + Name = $Name + Item = $InputObject + Level = $Level } break } 'custom' { - if (!$Level) { - $Level = 'Informational' - } + if (!$Level) { $Level = 'Informational' } $logItem = @{ Name = $Name Item = @{ @@ -1396,9 +1409,7 @@ function Write-PodeLog { break } 'exception' { - if (!$Level) { - $Level = 'Error' - } + if (!$Level) { $Level = 'Error' } $logItem = @{ Name = $Name Item = @{ @@ -1407,31 +1418,34 @@ function Write-PodeLog { Tag = $Tag } } - } } + + # Get the configured log method. $log = $PodeContext.Server.Logging.Type[$Name] + if ($log.Standard) { + # Add server details to the log item. $logItem.Item.Server = $PodeContext.Server.ComputerName - if ($log.Method.Arguments.AsUTC) { - $logItem.Item.Date = [datetime]::UtcNow - } - else { - $logItem.Item.Date = [datetime]::Now - } + # Add the current date and time (UTC or local) to the log item. + $logItem.Item.Date = if ($log.Method.Arguments.AsUTC) { [datetime]::UtcNow } else { [datetime]::Now } - if ($ThreadId) { - $logItem.Item.ThreadId = $ThreadId - } - else { - $logItem.Item.ThreadId = [System.Threading.Thread]::CurrentThread.ManagedThreadId - } - if ($PSCmdlet.ParameterSetName.ToLowerInvariant() -eq 'exception') { - Write-PodeErrorLog -Exception $Exception -Level $Level -CheckInnerException:$CheckInnerException -ThreadId $ThreadId + # Set the thread ID if provided, otherwise use the current thread ID. + $logItem.Item.ThreadId = if ($ThreadId) { $ThreadId } else { [System.Threading.Thread]::CurrentThread.ManagedThreadId } + + # If error logging is not suppressed, log errors or exceptions. + if (! $SuppressErrorLog.IsPresent) { + if ($PSCmdlet.ParameterSetName.ToLowerInvariant() -eq 'exception') { + Write-PodeErrorLog -Exception $Exception -Level $Level -CheckInnerException:$CheckInnerException -ThreadId $ThreadId + } + elseif ($Level -eq 'Error') { + Write-PodeErrorLog -Message $Message -Level $Level -ThreadId $ThreadId + } } } - # add the item to be processed + + # Enqueue the log item for processing. [Pode.PodeLogger]::Enqueue($logItem) } } From 7858e47a904939b383f3234897446dd5bf450eb4 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sun, 20 Oct 2024 09:34:08 -0700 Subject: [PATCH 10/53] removed redundant sg function --- src/Private/Logging.ps1 | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index 8144202fb..4ece98afa 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -285,14 +285,6 @@ function Get-PodeLoggingSysLogMethod { return { param($MethodId) - # Helper function to sanitize and return a default value if the input is null or whitespace - function sg($value) { - if ([string]::IsNullOrWhiteSpace($value)) { - return '-' - } - return $value - } - $log = @{} $socketCreated = $false try { From bd92d534b01c79cf2f5952cdfc279b4c1bffe96e Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sun, 20 Oct 2024 18:08:07 -0700 Subject: [PATCH 11/53] fix test --- src/Private/Logging.ps1 | 163 ++++++++-------------------------------- src/Public/Logging.ps1 | 99 +++++++++++------------- 2 files changed, 77 insertions(+), 185 deletions(-) diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index 4ece98afa..f918761d5 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -671,106 +671,41 @@ function Get-PodeLoggingInbuiltType { switch ($options.LogFormat.ToLowerInvariant()) { 'extended' { - $sb = [System.Text.StringBuilder]::new() - $null = $sb.Append('Date: ') - $null = $sb.Append($item.Date.ToString('yyyy-MM-dd')) - $null = $sb.Append(' ') - $null = $sb.Append($item.Date.ToString('HH:mm:ss')) - $null = $sb.Append(' ') - $null = $sb.Append((sg $item.Host)) - $null = $sb.Append(' ') - $null = $sb.Append((sg $item.User)) - $null = $sb.Append(' ') - $null = $sb.Append((sg $item.Request.Method)) - $null = $sb.Append(' ') - $null = $sb.Append((sg $item.Request.Resource)) - $null = $sb.Append(' ') - $null = $sb.Append((sg $item.Request.Query)) - $null = $sb.Append(' ') - $null = $sb.Append((sg $item.Response.StatusCode)) - $null = $sb.Append(' ') - $null = $sb.Append((sg $item.Response.Size)) - $null = $sb.Append(' "') - $null = $sb.Append((sg $item.Request.Agent)) - $null = $sb.Append('"') - return $sb.ToString() + return [System.Text.StringBuilder]::new(). + $sb.Append('Date: ').Append($item.Date.ToString('yyyy-MM-dd')).Append(' ').Append($item.Date.ToString('HH:mm:ss')).Append(' '). + $sb.Append((sg $item.Host)).Append(' ').Append((sg $item.User)).Append(' ').Append((sg $item.Request.Method)).Append(' '). + $sb.Append((sg $item.Request.Resource)).Append(' ').Append((sg $item.Request.Query)).Append(' ').Append((sg $item.Response.StatusCode)). + $sb.Append(' ').Append((sg $item.Response.Size)).Append(' "').Append((sg $item.Request.Agent)).Append('"').ToString() #return "$($item.Date.ToString('yyyy-MM-dd')) $($item.Date.ToString('HH:mm:ss')) $(sg $item.Host) $(sg $item.User) $(sg $item.Request.Method) $(sg $item.Request.Resource) $(sg $item.Request.Query) $(sg $item.Response.StatusCode) $(sg $item.Response.Size) `"$(sg $item.Request.Agent)`"" } 'common' { - $sb = [System.Text.StringBuilder]::new() - $null = $sb.Append((sg $item.Host)) - $null = $sb.Append(' ') - $null = $sb.Append((sg $item.RfcUserIdentity)) - $null = $sb.Append(' ') - $null = $sb.Append((sg $item.User)) - $null = $sb.Append(' [') - $null = $sb.Append(([regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2'))) - $null = $sb.Append('] "') - $null = $sb.Append((sg $item.Request.Method)) - $null = $sb.Append(' ') - $null = $sb.Append((sg $item.Request.Resource)) - $null = $sb.Append(' ') - $null = $sb.Append((sg $item.Request.Protocol)) - $null = $sb.Append('" ') - $null = $sb.Append((sg $item.Response.StatusCode)) - $null = $sb.Append(' ') - $null = $sb.Append((sg $item.Response.Size)) - return $sb.ToString() + return [System.Text.StringBuilder]::new() + Append((sg $item.Host)).Append(' ').Append((sg $item.RfcUserIdentity)).Append(' ').Append((sg $item.User)).Append(' ['). + Append(([regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2'))).Append('] "'). + Append((sg $item.Request.Method)).Append(' ').Append((sg $item.Request.Resource)).Append(' ').Append((sg $item.Request.Protocol)). + Append('" ').Append((sg $item.Response.StatusCode)).Append(' ').Append((sg $item.Response.Size)).ToString() # Build the URL with HTTP method # $url = "$(sg $item.Request.Method) $(sg $item.Request.Resource) $(sg $item.Request.Protocol)" # $date = [regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2') # return "$(sg $item.Host) $(sg $item.RfcUserIdentity) $(sg $item.User) [$date] `"$($url)`" $(sg $item.Response.StatusCode) $(sg $item.Response.Size)" } 'json' { - $sb = [System.Text.StringBuilder]::new() - $null = $sb.Append('{"time": "') - $null = $sb.Append($item.Date.ToString('yyyy-MM-ddTHH:mm:ssK')) - $null = $sb.Append('","remote_ip": "') - $null = $sb.Append((sg $item.Host)) - $null = $sb.Append('","user": "') - $null = $sb.Append((sg $item.User)) - $null = $sb.Append('","method": "') - $null = $sb.Append((sg $item.Request.Method)) - $null = $sb.Append('","uri": "') - $null = $sb.Append((sg $item.Request.Resource)) - $null = $sb.Append('","query": "') - $null = $sb.Append((sg $item.Request.Query)) - $null = $sb.Append('","status": ') - $null = $sb.Append((sg $item.Response.StatusCode)) - $null = $sb.Append(',"response_size": ') - $null = $sb.Append((sg $item.Response.Size)) - $null = $sb.Append(',"user_agent": "') - $null = $sb.Append((sg $item.Request.Agent)) - $null = $sb.Append('"}') - return $sb.ToString() + return [System.Text.StringBuilder]::new(). + Append('{"time": "').Append($item.Date.ToString('yyyy-MM-ddTHH:mm:ssK')).Append('","remote_ip": "').Append((sg $item.Host)). + Append('","user": "').Append((sg $item.User)).Append('","method": "').Append((sg $item.Request.Method)).Append('","uri": "'). + Append((sg $item.Request.Resource)).Append('","query": "').Append((sg $item.Request.Query)).Append('","status": '). + Append((sg $item.Response.StatusCode)).Append(',"response_size": ').Append((sg $item.Response.Size)). + Append(',"user_agent": "').Append((sg $item.Request.Agent)).Append('"}').ToString() # return "{`"time`": `"$($item.Date.ToString('yyyy-MM-ddTHH:mm:ssK'))`",`"remote_ip`": `"$(sg $item.Host)`",`"user`": `"$(sg $item.User)`",`"method`": `"$(sg $item.Request.Method)`",`"uri`": `"$(sg $item.Request.Resource)`",`"query`": `"$(sg $item.Request.Query)`",`"status`": $(sg $item.Response.StatusCode),`"response_size`": $(sg $item.Response.Size),`"user_agent`": `"$(sg $item.Request.Agent)`"}" } # Combined is the default default { - $sb = [System.Text.StringBuilder]::new() - $null = $sb.Append((sg $item.Host)) - $null = $sb.Append(' ') - $null = $sb.Append((sg $item.RfcUserIdentity)) - $null = $sb.Append(' ') - $null = $sb.Append((sg $item.User)) - $null = $sb.Append(' [') - $null = $sb.Append(([regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2'))) - $null = $sb.Append('] "') - $null = $sb.Append((sg $item.Request.Method)) - $null = $sb.Append(' ') - $null = $sb.Append((sg $item.Request.Resource)) - $null = $sb.Append(' ') - $null = $sb.Append((sg $item.Request.Protocol)) - $null = $sb.Append('" ') - $null = $sb.Append((sg $item.Response.StatusCode)) - $null = $sb.Append(' ') - $null = $sb.Append((sg $item.Response.Size)) - $null = $sb.Append(' "') - $null = $sb.Append((sg $item.Request.Referrer)) - $null = $sb.Append('" "') - $null = $sb.Append((sg $item.Request.Agent)) - $null = $sb.Append('"') - return $sb.ToString() + return [System.Text.StringBuilder]::new().Append((sg $item.Host)).Append(' ').Append((sg $item.RfcUserIdentity)).Append(' ').Append((sg $item.User)). + Append(' [').Append(([regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2'))). + Append('] "').Append((sg $item.Request.Method)).Append(' ').Append((sg $item.Request.Resource)).Append(' '). + Append((sg $item.Request.Protocol)).Append('" ').Append((sg $item.Response.StatusCode)).Append(' ').Append((sg $item.Response.Size)). + Append(' "').Append((sg $item.Request.Referrer)).Append('" "').Append((sg $item.Request.Agent)).Append('"').ToString() + # Build the URL with HTTP method # $url = "$(sg $item.Request.Method) $(sg $item.Request.Resource) $(sg $item.Request.Protocol)" # $date = [regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2') @@ -797,26 +732,10 @@ function Get-PodeLoggingInbuiltType { } - $sb = [System.Text.StringBuilder]::new() - - # Append the details to the StringBuilder - $null = $sb.Append('Date: ') - $null = $sb.AppendLine($item.Date.ToString($options.DataFormat)) - $null = $sb.Append('Level: ') - $null = $sb.AppendLine($item.Level) - $null = $sb.Append('ThreadId: ') - $null = $sb.AppendLine($item.ThreadId) - $null = $sb.Append('Server: ') - $null = $sb.AppendLine($item.Server) - $null = $sb.Append('Category: ') - $null = $sb.AppendLine($item.Category) - $null = $sb.Append('Message: ') - $null = $sb.AppendLine($item.Message) - $null = $sb.Append('StackTrace: ') - $null = $sb.AppendLine($item.StackTrace) - - # Return the built string - return $sb.ToString() + return [System.Text.StringBuilder]::new(). + Append('Date: ').Append($item.Date.ToString($options.DataFormat)).Append('Level: ').Append($item.Level). + Append('ThreadId: ').Append($item.ThreadId).Append('Server: ').Append($item.Server).Append('Category: '). + Append($item.Category).Append('Message: ').Append($item.Message).Append('StackTrace: ').Append($item.StackTrace).ToString() } } @@ -834,18 +753,9 @@ function Get-PodeLoggingInbuiltType { return $item } # Optimized concatenation using Append - $sb = [System.Text.StringBuilder]::new() - $null = $sb.Append('[') - $null = $sb.Append($item.Date.ToString($options.DataFormat)) - $null = $sb.Append('] ') - $null = $sb.Append($item.Level) - $null = $sb.Append(' ') - $null = $sb.Append($item.Tag) - $null = $sb.Append(' ') - $null = $sb.Append($item.ThreadId) - $null = $sb.Append(' ') - $null = $sb.Append($item.Message) - return $sb.ToString() + return [System.Text.StringBuilder]::new(). + Append('[').Append($item.Date.ToString($options.DataFormat)).Append('] '). + Append($item.Level).Append(' ').Append($item.Tag).Append(' ').Append($item.ThreadId).Append(' ').Append($item.Message).ToString() #return "[$($item.Date.ToString($options.DataFormat))] $($item.Level) $( $item.Tag) $($item.ThreadId) $($item.Message)" } } @@ -859,18 +769,9 @@ function Get-PodeLoggingInbuiltType { return $item } # Optimized concatenation using Append - $sb = [System.Text.StringBuilder]::new() - $null = $sb.Append('[') - $null = $sb.Append($item.Date.ToString($options.DataFormat)) - $null = $sb.Append('] ') - $null = $sb.Append($item.Level) - $null = $sb.Append(' ') - $null = $sb.Append($item.Tag) - $null = $sb.Append(' ') - $null = $sb.Append($item.ThreadId) - $null = $sb.Append(' ') - $null = $sb.Append($item.Message) - return $sb.ToString() + return [System.Text.StringBuilder]::new(). + Append('[').Append($item.Date.ToString($options.DataFormat)).Append('] '). + Append($item.Level).Append(' ').Append($item.Tag).Append(' ').Append($item.ThreadId).Append(' ').Append($item.Message).ToString() # return "[$($item.Date.ToString($options.DataFormat))] $($item.Level) $( $item.Tag) $($item.ThreadId) $($item.Message)" } } diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 750aef96e..3d30ba742 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -1151,86 +1151,81 @@ if (!(Test-Path Alias:Clear-PodeLoggers)) { <# .SYNOPSIS - Writes an Exception or ErrorRecord using the built-in error logging. + Logs an Exception, ErrorRecord, or a custom error message using Pode's built-in logging mechanism. .DESCRIPTION - This function logs an Exception or ErrorRecord using Pode's built-in error logging mechanism. It allows specifying the error level and optionally checks for inner exceptions. + This function logs exceptions, error records, or custom error messages with optional error categories and levels. It can also log inner exceptions and associate the error with a specific thread ID. Error levels can be set, and inner exceptions can be checked for more detailed logging. .PARAMETER Exception - An Exception to log. + The exception object to log. This is used when logging caught exceptions. .PARAMETER ErrorRecord - An ErrorRecord to log. + The error record to log. This is used when handling errors through PowerShell's error handling mechanism. -.PARAMETER ErrorMessage - A custom error message to log. +.PARAMETER Message + A custom error message to log when exceptions or error records are not available. .PARAMETER Category - The error category for the custom error message (Default: NotSpecified). + The category of the custom error message (Default: NotSpecified). .PARAMETER Level - The level of the error being logged. Options are: Error, Warning, Informational, Verbose, Debug (Default: Error). + The logging level for the error. Supported levels are: Error, Warning, Informational, Verbose, Debug (Default: Error). .PARAMETER CheckInnerException - If specified, any inner exceptions of the provided exception are also logged. + If specified, logs any inner exceptions associated with the provided exception. .PARAMETER ThreadId - The ID of the thread where the error occurred. + The ID of the thread where the error occurred. If not specified, the current thread's ID is used. + +.EXAMPLE + try { + # Some operation + } catch { + $_ | Write-PodeErrorLog + } .EXAMPLE - try { /* logic */ } catch { $_ | Write-PodeErrorLog } + [System.Exception]::new('Custom error message') | Write-PodeErrorLog -CheckInnerException .EXAMPLE - [System.Exception]::new('error message') | Write-PodeErrorLog + Write-PodeErrorLog -Message "Custom message" -Category NotSpecified -Level 'Warning' #> function Write-PodeErrorLog { [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Exception')] - [System.Exception] - $Exception, + [System.Exception] $Exception, [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ErrorRecord')] - [System.Management.Automation.ErrorRecord] - $ErrorRecord, + [System.Management.Automation.ErrorRecord] $ErrorRecord, [Parameter(Mandatory = $true, ParameterSetName = 'Message')] - [string] - $Message, + [string] $Message, - [Parameter( ParameterSetName = 'Message')] - [System.Management.Automation.ErrorCategory] - $Category = [System.Management.Automation.ErrorCategory]::NotSpecified, + [Parameter(ParameterSetName = 'Message')] + [System.Management.Automation.ErrorCategory] $Category = [System.Management.Automation.ErrorCategory]::NotSpecified, [Parameter()] [ValidateNotNullOrEmpty()] [ValidateSet('Error', 'Warning', 'Informational', 'Verbose', 'Debug')] - [string] - $Level = 'Error', + [string] $Level = 'Error', [Parameter(ParameterSetName = 'Exception')] - [switch] - $CheckInnerException, + [switch] $CheckInnerException, [Parameter()] - [int] - $ThreadId + [int] $ThreadId ) Process { - # do nothing if logging is disabled, or error logging isn't setup + # Check if logging is enabled and the error level is valid $name = Get-PodeErrorLoggingName - if (!(Test-PodeLoggerEnabled -Name $name)) { - return - } + if (!(Test-PodeLoggerEnabled -Name $name)) { return } - # do nothing if the error level isn't present $levels = @(Get-PodeErrorLoggingLevel) - if ($levels -inotcontains $Level) { - return - } + if ($levels -inotcontains $Level) { return } - # build error object for what we need + # Build the error object switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'exception' { $item = @{ @@ -1239,15 +1234,14 @@ function Write-PodeErrorLog { StackTrace = $Exception.StackTrace } } - - 'ErrorRecord' { + 'errorrecord' { $item = @{ Category = $ErrorRecord.CategoryInfo.ToString() Message = $ErrorRecord.Exception.Message StackTrace = $ErrorRecord.ScriptStackTrace } } - 'Message' { + 'message' { $item = @{ Category = $Category.ToString() Message = $Message @@ -1255,32 +1249,29 @@ function Write-PodeErrorLog { } } - # add general info + # General info and thread id $item['Server'] = $PodeContext.Server.ComputerName $item['Level'] = $Level - if ($PodeContext.Server.Logging.Type[$Name].Method.Arguments.AsUTC) { - $Item.Date = [datetime]::UtcNow - } - else { - $Item.Date = [datetime]::Now + $item['Date'] = if ($PodeContext.Server.Logging.Type[$Name].Method.Arguments.AsUTC) { + [datetime]::UtcNow + } else { + [datetime]::Now } - if ($ThreadId) { - $Item['ThreadId'] = $ThreadId - } - else { - $item['ThreadId'] = [System.Threading.Thread]::CurrentThread.ManagedThreadId #[int]$ThreadId + $item['ThreadId'] = if ($ThreadId) { + $ThreadId + } else { + [System.Threading.Thread]::CurrentThread.ManagedThreadId } + # Add the item to the logger queue $logItem = @{ Name = $name Item = $item } + [Pode.PodeLogger]::Enqueue($logItem) - # add the item to be processed - $null = [Pode.PodeLogger]::Enqueue( $logItem) - - # for exceptions, check the inner exception + # Log inner exceptions if specified if ($CheckInnerException -and ($null -ne $Exception.InnerException) -and ![string]::IsNullOrWhiteSpace($Exception.InnerException.Message)) { $Exception.InnerException | Write-PodeErrorLog -Level $Level -ThreadId $ThreadId } From a0b4673203190d3147e54a3a19242a54d9711944 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sun, 20 Oct 2024 20:46:05 -0700 Subject: [PATCH 12/53] New-PodeLoggingMethod deprecation --- examples/Logging.ps1 | 6 +- src/Private/Logging.ps1 | 54 ++- src/Public/Logging.ps1 | 1001 ++++++++++++++++++++++----------------- 3 files changed, 635 insertions(+), 426 deletions(-) diff --git a/examples/Logging.ps1 b/examples/Logging.ps1 index b76d38c2f..01703b94b 100644 --- a/examples/Logging.ps1 +++ b/examples/Logging.ps1 @@ -60,7 +60,7 @@ Start-PodeServer -browse { } if ( $LoggingType -icontains 'file') { - $logging += New-PodeLoggingMethod -File -Name 'file' -MaxDays 4 -Format Simple -ISO8601 + $logging += New-PodeFileLoggingMethod -Name 'file' -MaxDays 4 -Format Simple -ISO8601 $requestLogging = New-PodeLoggingMethod -File -Name 'requests' -MaxDays 4 } @@ -72,7 +72,7 @@ Start-PodeServer -browse { $rawItem | Out-File './examples/logs/customLegacy_rawItem.log' -Append } - $logging += New-PodeLoggingMethod -Custom -UseRunspace -CustomOptions @{ 'opt1' = 'something'; 'opt2' = 'else' } -ScriptBlock { + $logging += New-PodeCustomLoggingMethod -UseRunspace -CustomOptions @{ 'opt1' = 'something'; 'opt2' = 'else' } -ScriptBlock { $item | Out-File './examples/logs/customWithRunspace.log' -Append $options | Out-File './examples/logs/customWithRunspace_options.log' -Append $rawItem | Out-File './examples/logs/customWithRunspace_rawItem.log' -Append @@ -84,7 +84,7 @@ Start-PodeServer -browse { } if ( $LoggingType -icontains 'syslog') { - $logging += New-PodeLoggingMethod -syslog -Server 127.0.0.1 -Transport UDP -AsUTC -ISO8601 -FailureAction Report + $logging += New-PodeSyslogLoggingMethod -Server 127.0.0.1 -Transport UDP -AsUTC -ISO8601 -FailureAction Report } if ($logging.Count -eq 0) { diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index f918761d5..e53df9df2 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -1409,4 +1409,56 @@ function Write-PodeTraceLog { Name = $name Item = $item }) -} \ No newline at end of file +} + + +function New-PodeLogBatchInfo { + # batch details + return @{ + Id = New-PodeGuid + Size = $Batch + Timeout = $BatchTimeout + LastUpdate = $null + Items = @() + RawItems = @() + } +} + + +function Test-PodeDateFormat { + param ( + [string]$DateFormat + ) + + $sampleDate = [DateTime]::Now + try { + # Try to format the sample date using the provided format + $formattedDate = $sampleDate.ToString($DateFormat) + + # Try to parse the formatted date back to a DateTime object using the same format + [DateTime]::ParseExact($formattedDate, $DateFormat, $null) + + # If no exceptions are thrown, the format is valid + return $true + } + catch { + # If an exception is thrown, the format is invalid + return $false + } +} + + +<# + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } + + +#> \ No newline at end of file diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 3d30ba742..be4487816 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -1,141 +1,607 @@ -<# -.SYNOPSIS - Create a new method of outputting logs. -.DESCRIPTION - Create a new method of outputting logs. -.PARAMETER Terminal - If supplied, will use the inbuilt Terminal logging output method. +function New-PodeTerminalLoggingMethod { + [CmdletBinding(DefaultParameterSetName = 'DataFormat')] + [OutputType([hashtable])] + param( + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ + Test-PodeDateFormat $_ + })] + [string] + $DataFormat, -.PARAMETER File - If supplied, will use the inbuilt File logging output method. + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601, -.PARAMETER Path - The File Path of where to store the logs. + [Parameter()] + [switch] + $AsUTC + ) -.PARAMETER Name - The File Name to prepend new log files using. + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } + # Terminal logging logic here + $methodId = New-PodeGuid + $PodeContext.Server.Logging.Method[$methodId] = @{ + ScriptBlock = (Get-PodeLoggingTerminalMethod) + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + } + return @{ + Id = $methodId + Batch = New-PodeLogBatchInfo + Logger = @() + Arguments = @{ + DataFormat = $DataFormat + AsUTC = $false + } + } +} -.PARAMETER Format - The format of the log entries for the File logging method. Options are: RFC3164, RFC5424, Simple, Default (Default: Default). -.PARAMETER Separator - The separator to use in log entries for the File logging method (Default: ' '). +function New-PodeFileLoggingMethod { + [CmdletBinding(DefaultParameterSetName = 'DataFormat')] + [OutputType([hashtable])] + param( + [string] + $Path = './logs', -.PARAMETER MaxLength - The maximum length of log entries for the File logging method (Default: -1). + [Parameter(Mandatory = $true)] + [string] + $Name, -.PARAMETER MaxDays - The maximum number of days to keep logs, before Pode automatically removes them. + [Parameter()] + [ValidateSet('RFC3164', 'RFC5424', 'Simple', 'Default')] + [string] + $Format = 'Default', -.PARAMETER MaxSize - The maximum size of a log file, before Pode starts writing to a new log file. + [Parameter()] + [string] + $Separator = ' ', -.PARAMETER EventViewer - If supplied, will use the inbuilt Event Viewer logging output method. + [Parameter()] + [int] + $MaxLength = -1, -.PARAMETER EventLogName - Optional Log Name for the Event Viewer (Default: Application) + [Parameter()] + [ValidateRange(0, [int]::MaxValue)] + [int] + $MaxDays = 0, -.PARAMETER Source - Optional Source for the Event Viewer (Default: Pode) + [Parameter()] + [ValidateRange(0, [int]::MaxValue)] + [int] + $MaxSize = 0, -.PARAMETER EventID - Optional EventID for the Event Viewer (Default: 0) + [Parameter()] + [string] + $Source = 'Pode', -.PARAMETER Batch - An optional batch size to write log items in bulk (Default: 1) + [Parameter()] + [ValidateSet('Ignore', 'Report', 'Halt')] + [string] + $FailureAction = 'Ignore', -.PARAMETER BatchTimeout - An optional batch timeout, in seconds, to send items off for writing if a log item isn't received (Default: 0) + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ + Test-PodeDateFormat $_ + })] + [string] + $DataFormat, -.PARAMETER Custom - If supplied, will allow you to create a Custom Logging output method. + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601, -.PARAMETER UseRunspace - If supplied, the Custom Logging output method will use its own separated runspace + [Parameter()] + [switch] + $AsUTC + ) -.PARAMETER CustomOptions - An hastable of properties to supply to the Custom Logging output method's ScriptBlock when used inside a runspace. - The content is available using the variable `$options` + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } -.PARAMETER ScriptBlock - The ScriptBlock that defines how to output a log item. + $Path = (Protect-PodeValue -Value $Path -Default './logs') + $Path = (Get-PodeRelativePath -Path $Path -JoinRoot -Resolve) + $null = New-Item -Path $Path -ItemType Directory -Force + $methodId = New-PodeGuid + $PodeContext.Server.Logging.Method[$methodId] = @{ + ScriptBlock = (Get-PodeLoggingFileMethod) + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + } -.PARAMETER ArgumentList - An array of arguments to supply to the Custom Logging output method's ScriptBlock. + return @{ + Id = $methodId + Batch = New-PodeLogBatchInfo + Logger = @() + Arguments = @{ + Name = $Name + Path = $Path + MaxDays = $MaxDays + MaxSize = $MaxSize + FileId = 0 + Date = $null + NextClearDown = [datetime]::Now.Date + FailureAction = $FailureAction + DataFormat = $DataFormat + AsUTC = $AsUTC + Encoding = $Encoding + Format = $Format + MaxLength = $MaxLength + Source = $Source + Separator = $Separator + } + } +} + +function New-PodeEventViewerLoggingMethod { + [CmdletBinding(DefaultParameterSetName = 'DataFormat')] + [OutputType([hashtable])] + param( + [string] + $EventLogName = 'Application', + + [string] + $Source = 'Pode', + + [int] + $EventID = 0, + + [ValidateSet('Ignore', 'Report', 'Halt')] + [string] + $FailureAction = 'Ignore', + + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ Test-PodeDateFormat $_ })] + [string] + $DataFormat, + + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601, + + [Parameter()] + [switch] + $AsUTC + + ) + + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } -.PARAMETER Syslog - If supplied, will use the Syslog logging output method. + if (!(Test-PodeIsWindows)) { + # Event Viewer logging only supported on Windows + throw ($PodeLocale.eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage) + } -.PARAMETER Server - The Syslog server to send logs to. + if (![System.Diagnostics.EventLog]::SourceExists($Source)) { + [System.Diagnostics.EventLog]::CreateEventSource($Source, $EventLogName) + } -.PARAMETER Port - The port on the Syslog server (Default: 514). + $methodId = New-PodeGuid + $PodeContext.Server.Logging.Method[$methodId] = @{ + ScriptBlock = (Get-PodeLoggingEventViewerMethod) + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + } + return @{ + Id = $methodId + Batch = New-PodeLogBatchInfo + Logger = @() + Arguments = @{ + LogName = $EventLogName + Source = $Source + ID = $EventID + FailureAction = $FailureAction + DataFormat = $DataFormat + AsUTC = $AsUTC + } + } +} -.PARAMETER Transport - The transport protocol to use (Default: UDP). +function New-PodeSyslogLoggingMethod { + [CmdletBinding(DefaultParameterSetName = 'DataFormat')] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $true)] + [string] + $Server, -.PARAMETER TlsProtocol - The TLS protocol version to use (Default: TLS 1.3). + [Parameter()] + [Int16] + $Port = 514, -.PARAMETER SyslogProtocol - The Syslog protocol to use (Default: RFC5424). + [Parameter()] + [ValidateSet('UDP', 'TCP', 'TLS')] + [string] + $Transport = 'UDP', -.PARAMETER Encoding - The encoding to use for the Syslog messages (Default: UTF8). + [Parameter()] + [System.Security.Authentication.SslProtocols] + $TlsProtocol = [System.Security.Authentication.SslProtocols]::Tls13, -.PARAMETER SkipCertificateCheck - Skip certificate validation for TLS connections. + [Parameter()] + [ValidateSet('RFC3164', 'RFC5424')] + [string] + $SyslogProtocol = 'RFC5424', -.PARAMETER Restful - If supplied, will use the Restful logging output method. + [Parameter()] + [ValidateSet('ASCII', 'BigEndianUnicode', 'Default', 'Unicode', 'UTF32', 'UTF7', 'UTF8')] + [string] + $Encoding = 'UTF8', -.PARAMETER BaseUrl - The base URL for the Restful logging endpoint. + [Parameter()] + [string] + $Source = 'Pode', + + [Parameter()] + [switch] + $SkipCertificateCheck, -.PARAMETER Platform - The platform for Restful logging (Splunk, LogInsight). + [Parameter()] + [ValidateSet('Ignore', 'Report', 'Halt')] + [string] + $FailureAction = 'Ignore', -.PARAMETER Token - The token for authentication with Restful servers that require it. + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ Test-PodeDateFormat $_ })] + [string] + $DataFormat, -.PARAMETER Id - The LogInsight collector ID. + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601, -.PARAMETER FailureAction - Defines the behavior in case of failure. Options are: Ignore, Report, Halt (Default: Ignore). + [Parameter()] + [switch] + $AsUTC -.PARAMETER DataFormat - The date format to use for the log entries (Default: 'dd/MMM/yyyy:HH:mm:ss zzz'). + ) -.PARAMETER ISO8601 - If set, the date format will be ISO 8601 compliant (equivalent to -DataFormat 'yyyy-MM-ddTHH:mm:ssK') - This parameter is mutually exclusive with DataFormat. + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } -.PARAMETER AsUTC - If set, the time will be logged in UTC instead of local time. + $selectedEncoding = [System.Text.Encoding]::$Encoding -.EXAMPLE - $term_logging = New-PodeLoggingMethod -Terminal + if ($null -eq $selectedEncoding) { + throw ($PodeLocale.invalidEncodingExceptionMessage -f $Encoding) + } + + $methodId = New-PodeGuid + $PodeContext.Server.Logging.Method[$methodId] = @{ + ScriptBlock = (Get-PodeLoggingSysLogMethod) + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + } + + return @{ + Id = $methodId + Batch = New-PodeLogBatchInfo + Logger = @() + Arguments = @{ + Server = $Server + Port = $Port + Transport = $Transport + Hostname = $Hostname + Source = $Source + TslProtocols = $TlsProtocol + SkipCertificateCheck = $SkipCertificateCheck + SyslogProtocol = $SyslogProtocol + Encoding = $selectedEncoding + FailureAction = $FailureAction + DataFormat = $DataFormat + AsUTC = $AsUTC + } + } +} + + +function New-PodeRestfulLoggingMethod { + [CmdletBinding(DefaultParameterSetName = 'DataFormat')] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $true)] + [ValidatePattern('^(https?://|/).+')] + [string] + $BaseUrl, + + [ValidateSet('Splunk', 'LogInsight')] + [string]$Platform = 'Splunk', + + [Parameter()] + [string] + $Token, + + [Parameter()] + [string] + $Id, + + [Parameter()] + [string] + $Source = 'Pode', + + [Parameter()] + [switch] + $SkipCertificateCheck, + + [Parameter()] + [ValidateSet('Ignore', 'Report', 'Halt')] + [string] + $FailureAction = 'Ignore', + + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ Test-PodeDateFormat $_ })] + [string] + $DataFormat, + + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601, + + [Parameter()] + [switch] + $AsUTC + ) + + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } + + $methodId = New-PodeGuid + $PodeContext.Server.Logging.Method[$methodId] = @{ + ScriptBlock = (Get-PodeLoggingRestfulMethod) + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + } + return @{ + Id = $methodId + Batch = New-PodeLogBatchInfo + Logger = @() + Arguments = @{ + Platform = $Platform + Hostname = $Hostname + Source = $Source + SkipCertificateCheck = $SkipCertificateCheck + Token = $Token + Id = $Id + FailureAction = $FailureAction + DataFormat = $DataFormat + AsUTC = $AsUTC + } + } +} -.EXAMPLE - $file_logging = New-PodeLoggingMethod -File -Path ./logs -Name 'requests' + + +function New-PodeCustomLoggingMethod { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUSeDeclaredVarsMoreThanAssignments', '')] + [CmdletBinding(DefaultParameterSetName = 'RunSpace')] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $true)] + [ValidateScript({ + if (Test-PodeIsEmpty $_) { + # A non-empty ScriptBlock is required for the Custom logging output method + throw ($PodeLocale.nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage) + } + + return $true + }) + ] + [scriptblock] + $ScriptBlock, + + [Parameter()] + [object[]] + $ArgumentList, + + [Parameter(ParameterSetName = 'RunSpace_DataFormat')] + [Parameter(ParameterSetName = 'RunSpace_ISO8601')] + [Parameter(ParameterSetName = 'RunSpace')] + [switch] + $UseRunspace, + + [Parameter()] + [hashtable] + $CustomOptions = @{}, + + + [Parameter(ParameterSetName = 'RunSpace_DataFormat')] + [Parameter(ParameterSetName = 'RunSpace_ISO8601')] + [ValidateSet('Ignore', 'Report', 'Halt')] + [string] + $FailureAction = 'Ignore', + + + [Parameter(ParameterSetName = 'RunSpace_DataFormat')] + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ Test-PodeDateFormat $_ })] + [string] + $DataFormat, + + [Parameter(ParameterSetName = 'RunSpace_ISO8601')] + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601, + + [Parameter()] + [switch] + $AsUTC + ) + + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } + + $methodId = New-PodeGuid + + if ($UseRunspace.IsPresent) { + $enanchedScriptBlock = { + param($MethodId) + + $log = @{} + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + Start-Sleep -Milliseconds 100 + + if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { + if ($null -ne $log) { + $Item = $log.item + $Options = $log.options + $RawItem = $log.rawItem + try { + # Original ScriptBlock Start + <# ScriptBlock #> + # Original ScriptBlock End + } + catch { + Invoke-PodeHandleFailure -Message "Custom Logging $MethodId Error. message: $_" -FailureAction $options.FailureAction + } + } + } + } + } + $PodeContext.Server.Logging.Method[$methodId] = @{ + ScriptBlock = [ScriptBlock]::Create( $enanchedScriptBlock.ToString().Replace('<# ScriptBlock #>', $ScriptBlock.ToString())) + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + } + + return @{ + Id = $methodId + Batch = New-PodeLogBatchInfo + Logger = @() + Arguments = @{ + FailureAction = $FailureAction + DataFormat = $DataFormat + AsUTC = $AsUTC + } + $CustomOptions + } + } + else { + $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState + + return @{ + Id = $methodId + ScriptBlock = $ScriptBlock + UsingVariables = $usingVars + Batch = New-PodeLogBatchInfo + Logger = @() + Arguments = $ArgumentList + DataFormat = $DataFormat + AsUTC = $AsUTC + NoRunspace = $true + } + } +} + + + +<# +.SYNOPSIS +Create a new method of outputting logs. + +.DESCRIPTION +Create a new method of outputting logs. + +.PARAMETER Terminal +If supplied, will use the inbuilt Terminal logging output method. + +.PARAMETER File +If supplied, will use the inbuilt File logging output method. + +.PARAMETER Path +The File Path of where to store the logs. + +.PARAMETER Name +The File Name to prepend new log files using. + +.PARAMETER EventViewer +If supplied, will use the inbuilt Event Viewer logging output method. + +.PARAMETER EventLogName +Optional Log Name for the Event Viewer (Default: Application) + +.PARAMETER Source +Optional Source for the Event Viewer (Default: Pode) + +.PARAMETER EventID +Optional EventID for the Event Viewer (Default: 0) + +.PARAMETER Batch +An optional batch size to write log items in bulk (Default: 1) + +.PARAMETER BatchTimeout +An optional batch timeout, in seconds, to send items off for writing if a log item isn't received (Default: 0) + +.PARAMETER MaxDays +The maximum number of days to keep logs, before Pode automatically removes them. + +.PARAMETER MaxSize +The maximum size of a log file, before Pode starts writing to a new log file. + +.PARAMETER Custom +If supplied, will allow you to create a Custom Logging output method. + +.PARAMETER ScriptBlock +The ScriptBlock that defines how to output a log item. + +.PARAMETER ArgumentList +An array of arguments to supply to the Custom Logging output method's ScriptBlock. .EXAMPLE - $custom_logging = New-PodeLoggingMethod -Custom -ScriptBlock { /* logic */ } +$term_logging = New-PodeLoggingMethod -Terminal .EXAMPLE - $syslog_logging = New-PodeLoggingMethod -Syslog -Server '192.168.1.1' -Port 514 -Transport 'UDP' +$file_logging = New-PodeLoggingMethod -File -Path ./logs -Name 'requests' .EXAMPLE - $restful_logging = New-PodeLoggingMethod -Restful -BaseUrl 'https://logserver.example.com' -Platform 'Splunk' -Token 'your-token' +$custom_logging = New-PodeLoggingMethod -Custom -ScriptBlock { /* logic */ } #> function New-PodeLoggingMethod { [CmdletBinding(DefaultParameterSetName = 'Terminal')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUSeDeclaredVarsMoreThanAssignments', '')] [OutputType([hashtable])] param( [Parameter(ParameterSetName = 'Terminal')] @@ -154,19 +620,6 @@ function New-PodeLoggingMethod { [string] $Name, - [Parameter( ParameterSetName = 'File')] - [ValidateSet('RFC3164' , 'RFC5424', 'Simple', 'Default' )] - [string] - $Format = 'Default', - - [Parameter( ParameterSetName = 'File')] - [string] - $Separator = ' ', - - [Parameter( ParameterSetName = 'File')] - [int] - $MaxLength = -1, - [Parameter(ParameterSetName = 'EventViewer')] [switch] $EventViewer, @@ -176,8 +629,6 @@ function New-PodeLoggingMethod { $EventLogName = 'Application', [Parameter(ParameterSetName = 'EventViewer')] - [Parameter(ParameterSetName = 'Syslog')] - [Parameter(ParameterSetName = 'File')] [string] $Source = 'Pode', @@ -217,21 +668,11 @@ function New-PodeLoggingMethod { [int] $MaxSize = 0, - [Parameter(Mandatory = $true, ParameterSetName = 'Custom')] - [Parameter(Mandatory = $true, ParameterSetName = 'CustomRunspace')] + [Parameter(ParameterSetName = 'Custom')] [switch] $Custom, - [Parameter(Mandatory = $true, ParameterSetName = 'CustomRunspace')] - [switch] - $UseRunspace, - - [Parameter(ParameterSetName = 'CustomRunspace')] - [hashtable] - $CustomOptions = @{}, - [Parameter(Mandatory = $true, ParameterSetName = 'Custom')] - [Parameter(Mandatory = $true, ParameterSetName = 'CustomRunspace')] [ValidateScript({ if (Test-PodeIsEmpty $_) { # A non-empty ScriptBlock is required for the Custom logging output method @@ -245,334 +686,48 @@ function New-PodeLoggingMethod { [Parameter(ParameterSetName = 'Custom')] [object[]] - $ArgumentList, - - [Parameter(Mandatory = $true, ParameterSetName = 'Syslog')] - [switch] - $Syslog, - - [Parameter(Mandatory = $true, ParameterSetName = 'Syslog')] - [string] - $Server, - - [Parameter( ParameterSetName = 'Syslog')] - [Int16] - $Port = 514, - - [Parameter( ParameterSetName = 'Syslog')] - [ValidateSet('UDP', 'TCP', 'TLS' )] - [string] - $Transport = 'UDP', - - [Parameter( ParameterSetName = 'Syslog')] - [System.Security.Authentication.SslProtocols] - $TlsProtocol = [System.Security.Authentication.SslProtocols]::Tls13, - - [Parameter( ParameterSetName = 'Syslog')] - [ValidateSet('RFC3164' , 'RFC5424')] - [string] - $SyslogProtocol = 'RFC5424', - - [Parameter( ParameterSetName = 'Syslog')] - [Parameter( ParameterSetName = 'File')] - [ValidateSet('ASCII', 'BigEndianUnicode', 'Default', 'Unicode', 'UTF32', 'UTF7', 'UTF8')] - [string] - $Encoding = 'UTF8', - - [Parameter( ParameterSetName = 'Syslog')] - [Parameter(Mandatory = $true, ParameterSetName = 'Restful')] - [switch] - $SkipCertificateCheck, - - [Parameter(Mandatory = $true, ParameterSetName = 'Restful')] - [switch] - $Restful, - - [Parameter(Mandatory = $true, ParameterSetName = 'Restful')] - [ValidatePattern('^(https?://|/).+')] - [string] - $BaseUrl, - - [Parameter( ParameterSetName = 'Restful')] - [ValidateSet( 'Splunk', 'LogInsight')] - $Platform = 'Splunk', - - [Parameter( ParameterSetName = 'Restful')] - [string] - $Token, - - [Parameter( ParameterSetName = 'Restful')] - [string] - $Id, - - [Parameter(ParameterSetName = 'EventViewer')] - [Parameter(ParameterSetName = 'File')] - [Parameter(ParameterSetName = 'CustomRunspace')] - [Parameter( ParameterSetName = 'Restful')] - [Parameter( ParameterSetName = 'Syslog')] - [string] - [ValidateSet('Ignore', 'Report', 'Halt' )] - $FailureAction = 'Ignore', - - [Parameter()] - [ValidateScript({ - # Define a sample date to test the format - $sampleDate = [DateTime]::Now - try { - # Try to format the sample date using the provided format - $formattedDate = $sampleDate.ToString($_) - - # Try to parse the formatted date back to a DateTime object using the same format - [DateTime]::ParseExact($formattedDate, $_, $null) - - # If no exceptions are thrown, the format is valid - $true - } - catch { - # If an exception is thrown, the format is invalid - $false - } - })] - [string] - $DataFormat, - - [Parameter()] - [switch] - $ISO8601, - - [Parameter()] - [switch] - $AsUTC + $ArgumentList ) - if ((! [string]::IsNullOrEmpty($DataFormat)) -and $ISO8601.IsPresent) { - throw ("Parameters '{0}' and '{1}' are mutually exclusive." -f 'DataFormat', 'ISO8601') - } - if ($ISO8601.IsPresent) { - $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' - } - else { - $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format - } - # batch details - $batchInfo = @{ - Id = New-PodeGuid - Size = $Batch - Timeout = $BatchTimeout - LastUpdate = $null - Items = @() - RawItems = @() - } # return info on appropriate logging type switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'terminal' { - $methodId = New-PodeGuid - $PodeContext.Server.Logging.Method[$methodId] = @{ - ScriptBlock = (Get-PodeLoggingTerminalMethod) - Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() - } - return @{ - Id = $methodId - Batch = $batchInfo - Logger = @() - Arguments = @{ - DataFormat = $DataFormat - AsUTC = $AsUTC - } - } + return New-PodeTerminalLoggingMethod } 'file' { - $Path = (Protect-PodeValue -Value $Path -Default './logs') - $Path = (Get-PodeRelativePath -Path $Path -JoinRoot -Resolve) - $null = New-Item -Path $Path -ItemType Directory -Force - $methodId = New-PodeGuid - $PodeContext.Server.Logging.Method[$methodId] = @{ - ScriptBlock = (Get-PodeLoggingFileMethod) - Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() - } - return @{ - Id = $methodId - Batch = $batchInfo - Logger = @() - Arguments = @{ - Name = $Name - Path = $Path - MaxDays = $MaxDays - MaxSize = $MaxSize - FileId = 0 - Date = $null - NextClearDown = [datetime]::Now.Date - FailureAction = $FailureAction - DataFormat = $DataFormat - AsUTC = $AsUTC - Encoding = $Encoding - Format = $Format - MaxLength = $MaxLength - Source = $Source - Separator = $Separator - } - } - } - - 'eventviewer' { - # only windows - if (!(Test-PodeIsWindows)) { - # Event Viewer logging only supported on Windows - throw ($PodeLocale.eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage) - } - - # create source - if (![System.Diagnostics.EventLog]::SourceExists($Source)) { - $null = [System.Diagnostics.EventLog]::CreateEventSource($Source, $EventLogName) - } - - $methodId = New-PodeGuid - $PodeContext.Server.Logging.Method[$methodId] = @{ - ScriptBlock = (Get-PodeLoggingEventViewerMethod) - Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() - } - return @{ - Id = $methodId - Batch = $batchInfo - Logger = @() - Arguments = @{ - LogName = $EventLogName - Source = $Source - ID = $EventID - FailureAction = $FailureAction - DataFormat = $DataFormat - AsUTC = $AsUTC - } - } - } - 'syslog' { - # Get the encoding object based on the selected encoding name - $selectedEncoding = [System.Text.Encoding]::$Encoding - - if ($null -eq $selectedEncoding) { - throw ($PodeLocale.invalidEncodingExceptionMessage -f $Encoding) - } - - $methodId = New-PodeGuid - $PodeContext.Server.Logging.Method[$methodId] = @{ - ScriptBlock = (Get-PodeLoggingSysLogMethod) - Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() - } - return @{ - Id = $methodId - Batch = $batchInfo - Logger = @() - Arguments = @{ - Server = $Server - Port = $Port - Transport = $Transport - Hostname = $Hostname - Source = $Source - TslProtocols = $TlsProtocol - SkipCertificateCheck = $SkipCertificateCheck - SyslogProtocol = $SyslogProtocol - Encoding = $selectedEncoding - FailureAction = $FailureAction - DataFormat = $DataFormat - AsUTC = $AsUTC - } + $fileParams = @{ + Path = $PSBoundParameters['Path'] + Name = $PSBoundParameters['Name'] + MaxDays = $PSBoundParameters['MaxDays'] + MaxSize = $PSBoundParameters['MaxSize'] } + return New-PodeFileLoggingMethod @fileParams } - 'restful' { - $methodId = New-PodeGuid - $PodeContext.Server.Logging.Method[$methodId] = @{ - ScriptBlock = (Get-PodeLoggingRestfulMethod) - Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() - } - return @{ - Id = $methodId - Batch = $batchInfo - Logger = @() - Arguments = @{ - BaseUrl = $BaseUrl - Platform = $Platform - Hostname = $Hostname - Source = $Source - SkipCertificateCheck = $SkipCertificateCheck - Token = $Token - Id = $Id - FailureAction = $FailureAction - DataFormat = $DataFormat - AsUTC = $AsUTC - } + 'eventviewer' { + $eventViewerParams = @{ + EventLogName = $PSBoundParameters['EventLogName'] + Source = $PSBoundParameters['Source'] + EventID = $PSBoundParameters['EventID'] } + return New-PodeEventViewerLoggingMethod @eventViewerParams } - { ($_ -eq 'CustomRunspace') -or ($_ -eq 'custom') } { - $methodId = New-PodeGuid - if ($UseRunspace.IsPresent) { - $enanchedScriptBlock = { - param($MethodId) - - $log = @{} - while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { - Start-Sleep -Milliseconds 100 - - if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { - if ($null -ne $log) { - $Item = $log.item - $Options = $log.options - $RawItem = $log.rawItem - try { - # Original ScriptBlock Start - <# ScriptBlock #> - # Original ScriptBlock End - } - catch { - Invoke-PodeHandleFailure -Message "Custom Logging $MethodId Error. message: $_" -FailureAction $options.FailureAction - } - } - } - } - } + 'custom' { - $PodeContext.Server.Logging.Method[$methodId] = @{ - ScriptBlock = [ScriptBlock]::Create( $enanchedScriptBlock.ToString().Replace('<# ScriptBlock #>', $ScriptBlock.ToString())) - Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() - } - return @{ - Id = $methodId - Batch = $batchInfo - Logger = @() - Arguments = @{ - FailureAction = $FailureAction - DataFormat = $DataFormat - AsUTC = $AsUTC - } + $CustomOptions - } - } - else { - $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState - - return @{ - Id = $methodId - ScriptBlock = $ScriptBlock - UsingVariables = $usingVars - Batch = $batchInfo - Logger = @() - Arguments = $ArgumentList - FailureAction = $FailureAction - DataFormat = $DataFormat - AsUTC = $AsUTC - NoRunspace = $true - } + $customParams = @{ + ScriptBlock = $PSBoundParameters['ScriptBlock'] + ArgumentList = $PSBoundParameters['ArgumentList'] } - + return New-PodeCustomLoggingMethod @customParams } } } - <# .SYNOPSIS Enables Request Logging using a supplied output method. @@ -1254,13 +1409,15 @@ function Write-PodeErrorLog { $item['Level'] = $Level $item['Date'] = if ($PodeContext.Server.Logging.Type[$Name].Method.Arguments.AsUTC) { [datetime]::UtcNow - } else { + } + else { [datetime]::Now } $item['ThreadId'] = if ($ThreadId) { $ThreadId - } else { + } + else { [System.Threading.Thread]::CurrentThread.ManagedThreadId } From 94da198609c131e1a1bfe804769a07294f410b83 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sun, 20 Oct 2024 21:32:22 -0700 Subject: [PATCH 13/53] function headers --- src/Locales/ar/Pode.psd1 | 1 + src/Locales/de/Pode.psd1 | 1 + src/Locales/en-us/Pode.psd1 | 2 + src/Locales/en/Pode.psd1 | 1 + src/Locales/es/Pode.psd1 | 1 + src/Locales/fr/Pode.psd1 | 1 + src/Locales/it/Pode.psd1 | 1 + src/Locales/ja/Pode.psd1 | 1 + src/Locales/ko/Pode.psd1 | 1 + src/Locales/nl/Pode.psd1 | 1 + src/Locales/pl/Pode.psd1 | 1 + src/Locales/pt/Pode.psd1 | 1 + src/Locales/zh/Pode.psd1 | 1 + src/Pode.psd1 | 6 + src/Private/Logging.ps1 | 79 ++++++-- src/Public/Logging.ps1 | 375 +++++++++++++++++++++++++++++++++--- 16 files changed, 432 insertions(+), 42 deletions(-) diff --git a/src/Locales/ar/Pode.psd1 b/src/Locales/ar/Pode.psd1 index 9c5b8ffe2..926e3c157 100644 --- a/src/Locales/ar/Pode.psd1 +++ b/src/Locales/ar/Pode.psd1 @@ -292,4 +292,5 @@ getRequestBodyNotAllowedExceptionMessage = 'لا يمكن أن تحتوي عمليات {0} على محتوى الطلب.' fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "الدالة '{0}' لا تقبل مصفوفة كمدخل لأنبوب البيانات." unsupportedStreamCompressionEncodingExceptionMessage = 'تشفير الضغط غير مدعوم للتشفير {0}' + deprecatedFunctionWarningMessage = "تحذير: الدالة '{0}' مهملة وستتم إزالتها في الإصدارات المستقبلية. يُرجى استخدام الدالة '{1}' بدلاً منها." } diff --git a/src/Locales/de/Pode.psd1 b/src/Locales/de/Pode.psd1 index bc7524fcf..3ac6eab56 100644 --- a/src/Locales/de/Pode.psd1 +++ b/src/Locales/de/Pode.psd1 @@ -292,4 +292,5 @@ getRequestBodyNotAllowedExceptionMessage = '{0}-Operationen können keinen Anforderungstext haben.' fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "Die Funktion '{0}' akzeptiert kein Array als Pipeline-Eingabe." unsupportedStreamCompressionEncodingExceptionMessage = 'Die Stream-Komprimierungskodierung wird nicht unterstützt: {0}' + deprecatedFunctionWarningMessage = "WARNUNG: Die Funktion '{0}' ist veraltet und wird in zukünftigen Versionen entfernt. Bitte verwenden Sie stattdessen die Funktion '{1}'." } \ No newline at end of file diff --git a/src/Locales/en-us/Pode.psd1 b/src/Locales/en-us/Pode.psd1 index 8abd59892..e6625a30c 100644 --- a/src/Locales/en-us/Pode.psd1 +++ b/src/Locales/en-us/Pode.psd1 @@ -292,4 +292,6 @@ getRequestBodyNotAllowedExceptionMessage = '{0} operations cannot have a Request Body.' fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "The function '{0}' does not accept an array as pipeline input." unsupportedStreamCompressionEncodingExceptionMessage = 'Unsupported stream compression encoding: {0}' + deprecatedFunctionWarningMessage = "WARNING: The function '{0}' is deprecated and will be removed in future releases. Please use the '{1}' function instead." + } \ No newline at end of file diff --git a/src/Locales/en/Pode.psd1 b/src/Locales/en/Pode.psd1 index a307fcafa..87646e921 100644 --- a/src/Locales/en/Pode.psd1 +++ b/src/Locales/en/Pode.psd1 @@ -292,4 +292,5 @@ getRequestBodyNotAllowedExceptionMessage = '{0} operations cannot have a Request Body.' fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "The function '{0}' does not accept an array as pipeline input." unsupportedStreamCompressionEncodingExceptionMessage = 'Unsupported stream compression encoding: {0}' + deprecatedFunctionWarningMessage = "WARNING: The function '{0}' is deprecated and will be removed in future releases. Please use the '{1}' function instead." } \ No newline at end of file diff --git a/src/Locales/es/Pode.psd1 b/src/Locales/es/Pode.psd1 index ac5bae926..8922c0f68 100644 --- a/src/Locales/es/Pode.psd1 +++ b/src/Locales/es/Pode.psd1 @@ -292,4 +292,5 @@ getRequestBodyNotAllowedExceptionMessage = 'Las operaciones {0} no pueden tener un cuerpo de solicitud.' fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "La función '{0}' no acepta una matriz como entrada de canalización." unsupportedStreamCompressionEncodingExceptionMessage = 'La codificación de compresión de transmisión no es compatible: {0}' + deprecatedFunctionWarningMessage = "ADVERTENCIA: La función '{0}' está obsoleta y será eliminada en futuras versiones. Por favor, use la función '{1}' en su lugar." } \ No newline at end of file diff --git a/src/Locales/fr/Pode.psd1 b/src/Locales/fr/Pode.psd1 index 26d7107fa..84805b069 100644 --- a/src/Locales/fr/Pode.psd1 +++ b/src/Locales/fr/Pode.psd1 @@ -292,4 +292,5 @@ getRequestBodyNotAllowedExceptionMessage = 'Les opérations {0} ne peuvent pas avoir de corps de requête.' fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "La fonction '{0}' n'accepte pas un tableau en tant qu'entrée de pipeline." unsupportedStreamCompressionEncodingExceptionMessage = "La compression de flux {0} n'est pas prise en charge." + deprecatedFunctionWarningMessage = "AVERTISSEMENT : La fonction '{0}' est obsolète et sera supprimée dans les versions futures. Veuillez utiliser la fonction '{1}' à la place." } \ No newline at end of file diff --git a/src/Locales/it/Pode.psd1 b/src/Locales/it/Pode.psd1 index b5f1c1f38..1e2b80944 100644 --- a/src/Locales/it/Pode.psd1 +++ b/src/Locales/it/Pode.psd1 @@ -292,4 +292,5 @@ getRequestBodyNotAllowedExceptionMessage = 'Le operazioni {0} non possono avere un corpo della richiesta.' fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "La funzione '{0}' non accetta una matrice come input della pipeline." unsupportedStreamCompressionEncodingExceptionMessage = 'La compressione dello stream non è supportata per la codifica {0}' + deprecatedFunctionWarningMessage = "AVVISO: La funzione '{0}' è obsoleta e verrà rimossa nelle versioni future. Si prega di utilizzare la funzione '{1}' al suo posto." } \ No newline at end of file diff --git a/src/Locales/ja/Pode.psd1 b/src/Locales/ja/Pode.psd1 index d87e2f9f1..520669d9c 100644 --- a/src/Locales/ja/Pode.psd1 +++ b/src/Locales/ja/Pode.psd1 @@ -292,4 +292,5 @@ getRequestBodyNotAllowedExceptionMessage = '{0}操作にはリクエストボディを含めることはできません。' fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "関数 '{0}' は配列をパイプライン入力として受け付けません。" unsupportedStreamCompressionEncodingExceptionMessage = 'サポートされていないストリーム圧縮エンコーディングが提供されました: {0}' + deprecatedFunctionWarningMessage = "警告: 関数 '{0}' は廃止され、将来のリリースで削除されます。代わりに '{1}' 関数を使用してください。" } \ No newline at end of file diff --git a/src/Locales/ko/Pode.psd1 b/src/Locales/ko/Pode.psd1 index b2babcb7c..d24446797 100644 --- a/src/Locales/ko/Pode.psd1 +++ b/src/Locales/ko/Pode.psd1 @@ -292,4 +292,5 @@ getRequestBodyNotAllowedExceptionMessage = '{0} 작업에는 요청 본문이 있을 수 없습니다.' fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "함수 '{0}'은(는) 배열을 파이프라인 입력으로 받지 않습니다." unsupportedStreamCompressionEncodingExceptionMessage = '지원되지 않는 스트림 압축 인코딩: {0}' + deprecatedFunctionWarningMessage = "경고: 함수 '{0}'는 더 이상 지원되지 않으며, 향후 릴리스에서 제거될 예정입니다. 대신 '{1}' 함수를 사용하세요." } \ No newline at end of file diff --git a/src/Locales/nl/Pode.psd1 b/src/Locales/nl/Pode.psd1 index 2cf45b83d..4a1f6254f 100644 --- a/src/Locales/nl/Pode.psd1 +++ b/src/Locales/nl/Pode.psd1 @@ -292,4 +292,5 @@ getRequestBodyNotAllowedExceptionMessage = '{0}-operaties kunnen geen Request Body hebben.' fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "De functie '{0}' accepteert geen array als pipeline-invoer." unsupportedStreamCompressionEncodingExceptionMessage = 'Niet-ondersteunde streamcompressie-encodering: {0}' + deprecatedFunctionWarningMessage = "WAARSCHUWING: De functie '{0}' is verouderd en zal in toekomstige versies worden verwijderd. Gebruik in plaats daarvan de functie '{1}'." } \ No newline at end of file diff --git a/src/Locales/pl/Pode.psd1 b/src/Locales/pl/Pode.psd1 index dd3c01d0d..1324d9099 100644 --- a/src/Locales/pl/Pode.psd1 +++ b/src/Locales/pl/Pode.psd1 @@ -292,4 +292,5 @@ getRequestBodyNotAllowedExceptionMessage = 'Operacje {0} nie mogą mieć treści żądania.' fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "Funkcja '{0}' nie akceptuje tablicy jako wejścia potoku." unsupportedStreamCompressionEncodingExceptionMessage = 'Kodowanie kompresji strumienia nie jest obsługiwane: {0}' + deprecatedFunctionWarningMessage = "OSTRZEŻENIE: Funkcja '{0}' jest przestarzała i zostanie usunięta w przyszłych wersjach. Użyj funkcji '{1}' zamiast niej." } \ No newline at end of file diff --git a/src/Locales/pt/Pode.psd1 b/src/Locales/pt/Pode.psd1 index 5120cea24..784efe940 100644 --- a/src/Locales/pt/Pode.psd1 +++ b/src/Locales/pt/Pode.psd1 @@ -292,4 +292,5 @@ getRequestBodyNotAllowedExceptionMessage = 'As operações {0} não podem ter um corpo de solicitação.' fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "A função '{0}' não aceita uma matriz como entrada de pipeline." unsupportedStreamCompressionEncodingExceptionMessage = 'A codificação de compressão de fluxo não é suportada.' + deprecatedFunctionWarningMessage = "AVISO: A função '{0}' está obsoleta e será removida em versões futuras. Por favor, use a função '{1}' em seu lugar." } \ No newline at end of file diff --git a/src/Locales/zh/Pode.psd1 b/src/Locales/zh/Pode.psd1 index 97de6bbb1..1993597a3 100644 --- a/src/Locales/zh/Pode.psd1 +++ b/src/Locales/zh/Pode.psd1 @@ -292,4 +292,5 @@ getRequestBodyNotAllowedExceptionMessage = '{0} 操作不能包含请求体。' fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "函数 '{0}' 不接受数组作为管道输入。" unsupportedStreamCompressionEncodingExceptionMessage = '不支持的流压缩编码: {0}' + deprecatedFunctionWarningMessage = "警告:函数 '{0}' 已被弃用,并将在未来版本中移除。请改用 '{1}' 函数。" } \ No newline at end of file diff --git a/src/Pode.psd1 b/src/Pode.psd1 index d7f365bd9..a60e26916 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -283,6 +283,12 @@ # logging 'New-PodeLoggingMethod', + 'New-PodeCustomLoggingMethod', + 'New-PodeEventViewerLoggingMethod', + 'New-PodeFileLoggingMethod', + 'New-PodeRestfulLoggingMethod', + 'New-PodeSyslogLoggingMethod', + 'New-PodeTerminalLoggingMethod' 'Enable-PodeRequestLogging', 'Enable-PodeErrorLogging', 'Enable-PodeCommonLogging', diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index e53df9df2..b84f6f040 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -1320,6 +1320,9 @@ function Test-PodeLoggerBatch { .EXAMPLE Write-PodeTraceLog -Message 'Custom log message.' + +.NOTES + This is an internal function and may change in future releases of Pode. #> function Write-PodeTraceLog { [CmdletBinding(DefaultParameterSetName = 'Parameter')] @@ -1411,6 +1414,35 @@ function Write-PodeTraceLog { }) } +<# +.SYNOPSIS + Creates a new log batch information object. + +.DESCRIPTION + The `New-PodeLogBatchInfo` function initializes and returns a hashtable that contains the details of a log batch, + including a unique batch identifier, size, timeout, and placeholders for items to be logged. + +.OUTPUTS + [hashtable] + Returns a hashtable with the following keys: + - `Id`: A unique identifier for the log batch, generated using `New-PodeGuid`. + - `Size`: The number of log items to be batched. + - `Timeout`: The timeout (in seconds) for sending log items if a new log isn't received. + - `LastUpdate`: Initially set to `$null`, this tracks the last time the batch was updated. + - `Items`: An empty array to hold formatted log items. + - `RawItems`: An empty array to hold unformatted/raw log items. + +.EXAMPLE + $logBatch = New-PodeLogBatchInfo -Batch 10 -BatchTimeout 30 + + This creates a new log batch with a size of 10 items and a timeout of 30 seconds before the batch is processed. + +.NOTES + This function is used for batching log items before they are processed. The size and timeout determine + how many items or how much time can pass before a batch of logs is processed. + + This is an internal function and may change in future releases of Pode. +#> function New-PodeLogBatchInfo { # batch details @@ -1424,7 +1456,38 @@ function New-PodeLogBatchInfo { } } +<# +.SYNOPSIS + Tests whether a given date format string is valid. + +.DESCRIPTION + The `Test-PodeDateFormat` function checks if a provided date format string can successfully format and parse a date. + It uses the current date and time to validate the format. If the format is valid, it returns `$true`. + If the format is invalid, it returns `$false`. + +.PARAMETER DateFormat + The date format string to be tested. This can be any custom date format supported by .NET. +.EXAMPLE + Test-PodeDateFormat -DateFormat 'yyyy-MM-dd' + + This command checks if the 'yyyy-MM-dd' date format is valid and returns `$true` if it is, or `$false` if it isn't. + +.EXAMPLE + Test-PodeDateFormat -DateFormat 'invalidFormat' + + This command tests the string 'invalidFormat' as a date format and returns `$false` since it's not a valid format. + +.OUTPUTS + [bool] + Returns `$true` if the provided date format string is valid, otherwise returns `$false`. + +.NOTES + This function attempts to format and then parse the current date using the provided date format string. + If an exception is thrown during the process, the format is deemed invalid. + + This is an internal function and may change in future releases of Pode. +#> function Test-PodeDateFormat { param ( [string]$DateFormat @@ -1446,19 +1509,3 @@ function Test-PodeDateFormat { return $false } } - - -<# - switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { - 'iso8601' { - $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' - } - default { - if ([string]::IsNullOrEmpty($DataFormat)) { - $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format - } - } - } - - -#> \ No newline at end of file diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index be4487816..e5f4fe94d 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -1,5 +1,36 @@ +<# +.SYNOPSIS + Creates a new terminal logging method in Pode. + +.DESCRIPTION + This function sets up a logging method that outputs log messages to the terminal using Pode's internal terminal logging logic. It allows specifying a custom date format, or uses the ISO 8601 format if requested. Additionally, it supports logging time in UTC. + +.PARAMETER DataFormat + The custom date format to use for log entries. If not provided, a default format of 'dd/MMM/yyyy:HH:mm:ss zzz' is used. + This parameter is mutually exclusive with the ISO8601 parameter. + +.PARAMETER ISO8601 + If set, the date format will follow ISO 8601 (equivalent to -DataFormat 'yyyy-MM-ddTHH:mm:ssK'). + This parameter is mutually exclusive with the DataFormat parameter. + +.PARAMETER AsUTC + If set, the time will be logged in UTC instead of local time. + +.OUTPUTS + Hashtable: Returns a hashtable containing the logging method configuration. + +.EXAMPLE + $logMethod = New-PodeTerminalLoggingMethod -DataFormat 'yyyy/MM/dd HH:mm:ss' + + Creates a terminal logging method using the specified custom date format. + +.EXAMPLE + $logMethod = New-PodeTerminalLoggingMethod -ISO8601 -AsUTC + + Creates a terminal logging method that logs messages using the ISO 8601 date format and logs the time in UTC. +#> function New-PodeTerminalLoggingMethod { [CmdletBinding(DefaultParameterSetName = 'DataFormat')] [OutputType([hashtable])] @@ -20,34 +51,96 @@ function New-PodeTerminalLoggingMethod { $AsUTC ) + # Determine the date format based on parameter set switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'iso8601' { + # Use ISO8601 format if specified $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' } default { + # Use default format if no DataFormat is provided if ([string]::IsNullOrEmpty($DataFormat)) { $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format } } } - # Terminal logging logic here + + # Terminal logging logic $methodId = New-PodeGuid $PodeContext.Server.Logging.Method[$methodId] = @{ ScriptBlock = (Get-PodeLoggingTerminalMethod) Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() } + + # Return the logging method configuration return @{ Id = $methodId Batch = New-PodeLogBatchInfo Logger = @() Arguments = @{ DataFormat = $DataFormat - AsUTC = $false + AsUTC = $AsUTC.IsPresent } } } +<# +.SYNOPSIS + Creates a new file-based logging method in Pode. + +.DESCRIPTION + This function sets up a logging method that outputs log messages to a file. It supports configuring log file paths, names, formats, sizes, and retention policies, along with various log formatting options such as custom date formats or ISO 8601. + +.PARAMETER Path + The file path where the logs will be stored. Defaults to './logs'. + +.PARAMETER Name + The base name for the log files. This parameter is mandatory. + +.PARAMETER Format + The format of the log entries. Supported options are: RFC3164, RFC5424, Simple, and Default (Default: Default). + +.PARAMETER Separator + The character(s) used to separate log fields in each entry. Defaults to a space (' '). + +.PARAMETER MaxLength + The maximum length of log entries. Defaults to -1 (no limit). + +.PARAMETER MaxDays + The maximum number of days to keep log files. Logs older than this will be removed automatically. Defaults to 0 (no automatic removal). + +.PARAMETER MaxSize + The maximum size of a log file in bytes. Once this size is exceeded, a new log file will be created. Defaults to 0 (no size limit). + +.PARAMETER Source + The source of the log entries. Defaults to 'Pode'. + +.PARAMETER FailureAction + Specifies the action to take if logging fails. Options are: Ignore, Report, Halt (Default: Ignore). + +.PARAMETER DataFormat + The custom date format for log entries. Mutually exclusive with ISO8601. + +.PARAMETER ISO8601 + If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. + +.PARAMETER AsUTC + If set, logs the time in UTC instead of the local time. + +.OUTPUTS + Hashtable: Returns a hashtable containing the logging method configuration. + +.EXAMPLE + $logMethod = New-PodeFileLoggingMethod -Path './logs' -Name 'requests' + + Creates a new file logging method that stores logs in the './logs' directory with the base name 'requests'. + +.EXAMPLE + $logMethod = New-PodeFileLoggingMethod -Name 'requests' -MaxDays 7 -MaxSize 100MB + + Creates a file logging method that keeps logs for 7 days and creates new files once the log file reaches 100MB in size. +#> function New-PodeFileLoggingMethod { [CmdletBinding(DefaultParameterSetName = 'DataFormat')] [OutputType([hashtable])] @@ -107,9 +200,10 @@ function New-PodeFileLoggingMethod { $AsUTC ) + # Determine the date format based on the parameter set switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'iso8601' { - $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format } default { if ([string]::IsNullOrEmpty($DataFormat)) { @@ -118,15 +212,21 @@ function New-PodeFileLoggingMethod { } } + # Resolve the log file path $Path = (Protect-PodeValue -Value $Path -Default './logs') $Path = (Get-PodeRelativePath -Path $Path -JoinRoot -Resolve) $null = New-Item -Path $Path -ItemType Directory -Force + + # Create a unique ID for this logging method $methodId = New-PodeGuid + + # Register the logging method in Pode's context $PodeContext.Server.Logging.Method[$methodId] = @{ ScriptBlock = (Get-PodeLoggingFileMethod) Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() } + # Return the logging method configuration return @{ Id = $methodId Batch = New-PodeLogBatchInfo @@ -141,7 +241,7 @@ function New-PodeFileLoggingMethod { NextClearDown = [datetime]::Now.Date FailureAction = $FailureAction DataFormat = $DataFormat - AsUTC = $AsUTC + AsUTC = $AsUTC.IsPresent Encoding = $Encoding Format = $Format MaxLength = $MaxLength @@ -151,6 +251,48 @@ function New-PodeFileLoggingMethod { } } +<# +.SYNOPSIS + Creates a new Event Viewer logging method in Pode. + +.DESCRIPTION + This function sets up a logging method that outputs log messages to the Windows Event Viewer. It allows configuring the log name, source, and event ID, along with date formatting options like custom formats or ISO 8601. + +.PARAMETER EventLogName + The name of the event log to write to. Defaults to 'Application'. + +.PARAMETER Source + The source of the log entries. Defaults to 'Pode'. + +.PARAMETER EventID + The ID of the event to log. Defaults to 0. + +.PARAMETER FailureAction + Specifies the action to take if logging fails. Options are: Ignore, Report, Halt (Default: Ignore). + +.PARAMETER DataFormat + The custom date format for log entries. Mutually exclusive with ISO8601. + +.PARAMETER ISO8601 + If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. + +.PARAMETER AsUTC + If set, logs the time in UTC instead of local time. + +.OUTPUTS + Hashtable: Returns a hashtable containing the logging method configuration. + +.EXAMPLE + $logMethod = New-PodeEventViewerLoggingMethod -EventLogName 'Application' -Source 'PodeApp' + + Creates a new Event Viewer logging method that writes to the 'Application' log with the source 'PodeApp'. + +.EXAMPLE + $logMethod = New-PodeEventViewerLoggingMethod -Source 'MyApp' -EventID 1001 -ISO8601 + + Creates a new Event Viewer logging method with ISO 8601 date format, writing to the 'MyApp' source and using event ID 1001. + +#> function New-PodeEventViewerLoggingMethod { [CmdletBinding(DefaultParameterSetName = 'DataFormat')] [OutputType([hashtable])] @@ -180,12 +322,12 @@ function New-PodeEventViewerLoggingMethod { [Parameter()] [switch] $AsUTC - ) + # Determine the date format based on parameter set switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'iso8601' { - $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format } default { if ([string]::IsNullOrEmpty($DataFormat)) { @@ -194,20 +336,25 @@ function New-PodeEventViewerLoggingMethod { } } + # Check if the platform is Windows if (!(Test-PodeIsWindows)) { - # Event Viewer logging only supported on Windows + # Event Viewer logging is only supported on Windows throw ($PodeLocale.eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage) } + # Ensure the event source exists in the Event Log if (![System.Diagnostics.EventLog]::SourceExists($Source)) { [System.Diagnostics.EventLog]::CreateEventSource($Source, $EventLogName) } + # Create the method ID and configure the logging method $methodId = New-PodeGuid $PodeContext.Server.Logging.Method[$methodId] = @{ ScriptBlock = (Get-PodeLoggingEventViewerMethod) Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() } + + # Return the logging method configuration return @{ Id = $methodId Batch = New-PodeLogBatchInfo @@ -218,11 +365,67 @@ function New-PodeEventViewerLoggingMethod { ID = $EventID FailureAction = $FailureAction DataFormat = $DataFormat - AsUTC = $AsUTC + AsUTC = $AsUTC.IsPresent } } } +<# +.SYNOPSIS + Creates a new Syslog logging method in Pode. + +.DESCRIPTION + This function sets up a logging method that sends log messages to a remote Syslog server. It supports various Syslog protocols (RFC3164, RFC5424), transports (UDP, TCP, TLS), and encoding formats. The function also allows for custom date formatting or ISO 8601 compliance and can skip certificate checks for TLS connections. + +.PARAMETER Server + The Syslog server to send logs to. This parameter is mandatory. + +.PARAMETER Port + The port on the Syslog server to send logs to. Defaults to 514. + +.PARAMETER Transport + The transport protocol to use. Supported values are UDP, TCP, and TLS. Defaults to UDP. + +.PARAMETER TlsProtocol + The TLS protocol version to use if TLS transport is selected. Defaults to TLS 1.3. + +.PARAMETER SyslogProtocol + The Syslog protocol to use for message formatting. Supported values are RFC3164 and RFC5424. Defaults to RFC5424. + +.PARAMETER Encoding + The encoding to use for Syslog messages. Supported values are ASCII, BigEndianUnicode, Default, Unicode, UTF32, UTF7, and UTF8. Defaults to UTF8. + +.PARAMETER Source + The source of the log entries. Defaults to 'Pode'. + +.PARAMETER SkipCertificateCheck + If set, skips certificate validation for TLS connections. + +.PARAMETER FailureAction + Specifies the action to take if logging fails. Options are: Ignore, Report, Halt (Default: Ignore). + +.PARAMETER DataFormat + The custom date format for log entries. Mutually exclusive with ISO8601. + +.PARAMETER ISO8601 + If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. + +.PARAMETER AsUTC + If set, logs the time in UTC instead of local time. + +.EXAMPLE + $logMethod = New-PodeSyslogLoggingMethod -Server '192.168.1.100' -Transport 'TCP' -SyslogProtocol 'RFC3164' + + Creates a new Syslog logging method that sends logs to the Syslog server at 192.168.1.100 using TCP and RFC3164 format. + +.EXAMPLE + $logMethod = New-PodeSyslogLoggingMethod -Server '192.168.1.100' -SyslogProtocol 'RFC5424' -ISO8601 -AsUTC + + Creates a Syslog logging method that uses RFC5424 format with ISO 8601 date formatting and logs time in UTC. + +.OUTPUTS + Hashtable: Returns a hashtable containing the Syslog logging method configuration. +#> function New-PodeSyslogLoggingMethod { [CmdletBinding(DefaultParameterSetName = 'DataFormat')] [OutputType([hashtable])] @@ -279,12 +482,12 @@ function New-PodeSyslogLoggingMethod { [Parameter()] [switch] $AsUTC - ) + # Determine the date format based on parameter set switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'iso8601' { - $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format } default { if ([string]::IsNullOrEmpty($DataFormat)) { @@ -293,18 +496,20 @@ function New-PodeSyslogLoggingMethod { } } + # Select encoding based on the provided value $selectedEncoding = [System.Text.Encoding]::$Encoding - if ($null -eq $selectedEncoding) { throw ($PodeLocale.invalidEncodingExceptionMessage -f $Encoding) } + # Create the method ID and configure the logging method $methodId = New-PodeGuid $PodeContext.Server.Logging.Method[$methodId] = @{ ScriptBlock = (Get-PodeLoggingSysLogMethod) Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() } + # Return the logging method configuration return @{ Id = $methodId Batch = New-PodeLogBatchInfo @@ -315,18 +520,67 @@ function New-PodeSyslogLoggingMethod { Transport = $Transport Hostname = $Hostname Source = $Source - TslProtocols = $TlsProtocol - SkipCertificateCheck = $SkipCertificateCheck + TlsProtocols = $TlsProtocol + SkipCertificateCheck = $SkipCertificateCheck.IsPresent SyslogProtocol = $SyslogProtocol Encoding = $selectedEncoding FailureAction = $FailureAction DataFormat = $DataFormat - AsUTC = $AsUTC + AsUTC = $AsUTC.IsPresent } } } +<# +.SYNOPSIS + Creates a new RESTful logging method in Pode. + +.DESCRIPTION + This function sets up a logging method that sends log messages to a RESTful endpoint. It supports different platforms like Splunk or LogInsight, as well as options for date formatting, skipping certificate validation, and handling failures. + +.PARAMETER BaseUrl + The base URL of the RESTful logging endpoint. This parameter is mandatory. + +.PARAMETER Platform + The platform for RESTful logging. Supported platforms are: Splunk and LogInsight. Defaults to 'Splunk'. + +.PARAMETER Token + An optional token for authentication with the RESTful logging endpoint, if required by the platform. + +.PARAMETER Id + The optional LogInsight collector ID. + +.PARAMETER Source + The source of the log entries. Defaults to 'Pode'. + +.PARAMETER SkipCertificateCheck + If set, skips certificate validation for HTTPS connections. + +.PARAMETER FailureAction + Specifies the action to take if logging fails. Options are: Ignore, Report, Halt (Default: Ignore). + +.PARAMETER DataFormat + The custom date format for log entries. Mutually exclusive with ISO8601. + +.PARAMETER ISO8601 + If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. + +.PARAMETER AsUTC + If set, logs the time in UTC instead of local time. +.OUTPUTS + Hashtable: Returns a hashtable containing the RESTful logging method configuration. + +.EXAMPLE + $logMethod = New-PodeRestfulLoggingMethod -BaseUrl 'https://logserver.example.com' -Platform 'Splunk' -Token 'your-token' + + Creates a RESTful logging method that sends logs to a Splunk server using the specified token for authentication. + +.EXAMPLE + $logMethod = New-PodeRestfulLoggingMethod -BaseUrl '/api/logs' -ISO8601 -AsUTC + + Creates a RESTful logging method that sends logs using ISO 8601 date format and logs time in UTC. +#> function New-PodeRestfulLoggingMethod { [CmdletBinding(DefaultParameterSetName = 'DataFormat')] [OutputType([hashtable])] @@ -374,9 +628,10 @@ function New-PodeRestfulLoggingMethod { $AsUTC ) + # Determine the date format based on parameter set switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'iso8601' { - $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format } default { if ([string]::IsNullOrEmpty($DataFormat)) { @@ -385,11 +640,14 @@ function New-PodeRestfulLoggingMethod { } } + # Create the method ID and configure the logging method $methodId = New-PodeGuid $PodeContext.Server.Logging.Method[$methodId] = @{ ScriptBlock = (Get-PodeLoggingRestfulMethod) Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() } + + # Return the logging method configuration return @{ Id = $methodId Batch = New-PodeLogBatchInfo @@ -398,18 +656,60 @@ function New-PodeRestfulLoggingMethod { Platform = $Platform Hostname = $Hostname Source = $Source - SkipCertificateCheck = $SkipCertificateCheck + SkipCertificateCheck = $SkipCertificateCheck.IsPresent Token = $Token Id = $Id FailureAction = $FailureAction DataFormat = $DataFormat - AsUTC = $AsUTC + AsUTC = $AsUTC.IsPresent } } } +<# +.SYNOPSIS + Creates a new custom logging method in Pode. + +.DESCRIPTION + This function sets up a custom logging method that uses a script block to define the logging logic. It supports the option to run the logging method in a separate runspace and allows for custom options, date formatting, and failure handling. + +.PARAMETER ScriptBlock + A non-empty script block that defines the custom logging logic. This parameter is mandatory. + +.PARAMETER ArgumentList + An array of arguments to pass to the custom script block. + +.PARAMETER UseRunspace + If set, the custom logging method will be executed in a separate runspace. + +.PARAMETER CustomOptions + A hashtable of custom options that will be passed to the script block when used inside a runspace. + +.PARAMETER FailureAction + Specifies the action to take if logging fails. Options are: Ignore, Report, Halt (Default: Ignore). +.PARAMETER DataFormat + The custom date format for log entries. Mutually exclusive with ISO8601. +.PARAMETER ISO8601 + If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. + +.PARAMETER AsUTC + If set, logs the time in UTC instead of local time. + +.EXAMPLE + $logMethod = New-PodeCustomLoggingMethod -ScriptBlock { param($logItem) Write-Output $logItem } -UseRunspace + + Creates a custom logging method using a script block that writes log items to the output. The method runs in a separate runspace. + +.EXAMPLE + $logMethod = New-PodeCustomLoggingMethod -ScriptBlock { param($logItem) Write-Output $logItem } -DataFormat 'yyyy/MM/dd HH:mm:ss' + + Creates a custom logging method with a custom date format. + +.OUTPUTS + Hashtable: Returns a hashtable containing the custom logging method configuration. +#> function New-PodeCustomLoggingMethod { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUSeDeclaredVarsMoreThanAssignments', '')] [CmdletBinding(DefaultParameterSetName = 'RunSpace')] @@ -421,7 +721,6 @@ function New-PodeCustomLoggingMethod { # A non-empty ScriptBlock is required for the Custom logging output method throw ($PodeLocale.nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage) } - return $true }) ] @@ -466,6 +765,7 @@ function New-PodeCustomLoggingMethod { $AsUTC ) + # Determine the date format based on the parameter set switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'iso8601' { $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' @@ -480,6 +780,7 @@ function New-PodeCustomLoggingMethod { $methodId = New-PodeGuid if ($UseRunspace.IsPresent) { + # Create the script block for the custom logging method running in a separate runspace $enanchedScriptBlock = { param($MethodId) @@ -504,8 +805,10 @@ function New-PodeCustomLoggingMethod { } } } + + # Register the enhanced script block in Pode's logging method $PodeContext.Server.Logging.Method[$methodId] = @{ - ScriptBlock = [ScriptBlock]::Create( $enanchedScriptBlock.ToString().Replace('<# ScriptBlock #>', $ScriptBlock.ToString())) + ScriptBlock = [ScriptBlock]::Create($enanchedScriptBlock.ToString().Replace('<# ScriptBlock #>', $ScriptBlock.ToString())) Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() } @@ -521,6 +824,7 @@ function New-PodeCustomLoggingMethod { } } else { + # Convert scoped variables for the script block if not using a runspace $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState return @{ @@ -537,20 +841,30 @@ function New-PodeCustomLoggingMethod { } } - - <# .SYNOPSIS -Create a new method of outputting logs. + Creates a new method for outputting logs (Deprecated). .DESCRIPTION -Create a new method of outputting logs. + This function has been deprecated and will be removed in future versions. It creates various logging methods such as Terminal, File, Event Viewer, and Custom logging. + Please use the appropriate new functions for each logging method: + - `New-PodeTerminalLoggingMethod` for terminal logging. + - `New-PodeFileLoggingMethod` for file logging. + - `New-PodeEventViewerLoggingMethod` for Event Viewer logging. + - `New-PodeCustomLoggingMethod` for custom logging. .PARAMETER Terminal -If supplied, will use the inbuilt Terminal logging output method. + Deprecated. Please use `New-PodeTerminalLoggingMethod` instead. + If supplied, will use the inbuilt Terminal logging output method. .PARAMETER File -If supplied, will use the inbuilt File logging output method. + Deprecated. Please use `New-PodeFileLoggingMethod` instead. + If supplied, will use the inbuilt File logging output method. + +.PARAMETER EventViewer + Deprecated. Please use `New-PodeEventViewerLoggingMethod` instead. + If supplied, will use the inbuilt Event Viewer logging output method. + .PARAMETER Path The File Path of where to store the logs. @@ -690,14 +1004,18 @@ function New-PodeLoggingMethod { ) - # return info on appropriate logging type switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'terminal' { + # WARNING: Function `New-PodeLoggingMethod` is deprecated. Please use '{0}' function instead. + Write-PodeHost ($PodeLocale.deprecatedFunctionWarningMessage -f 'New-PodeLoggingMethod', 'New-PodeTerminalLoggingMethod') -ForegroundColor Yellow + return New-PodeTerminalLoggingMethod } 'file' { + # WARNING: Function `New-PodeLoggingMethod` is deprecated. Please use '{0}' function instead. + Write-PodeHost ($PodeLocale.deprecatedFunctionWarningMessage -f 'New-PodeLoggingMethod', 'New-PodeFileLoggingMethod') -ForegroundColor Yellow $fileParams = @{ Path = $PSBoundParameters['Path'] @@ -709,6 +1027,9 @@ function New-PodeLoggingMethod { } 'eventviewer' { + # WARNING: Function `New-PodeLoggingMethod` is deprecated. Please use '{0}' function instead. + Write-PodeHost ($PodeLocale.deprecatedFunctionWarningMessage -f 'New-PodeLoggingMethod', 'New-PodeEventViewerLoggingMethod') -ForegroundColor Yellow + $eventViewerParams = @{ EventLogName = $PSBoundParameters['EventLogName'] Source = $PSBoundParameters['Source'] @@ -718,6 +1039,8 @@ function New-PodeLoggingMethod { } 'custom' { + # WARNING: Function `New-PodeLoggingMethod` is deprecated. Please use '{0}' function instead. + Write-PodeHost ($PodeLocale.deprecatedFunctionWarningMessage -f 'New-PodeLoggingMethod', 'New-PodeCustomLoggingMethod') -ForegroundColor Yellow $customParams = @{ ScriptBlock = $PSBoundParameters['ScriptBlock'] From 7e0514a07d147531b837a60193acedd42ee267c4 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Mon, 21 Oct 2024 08:07:53 -0700 Subject: [PATCH 14/53] Update Pode.psd1 --- src/Locales/en-us/Pode.psd1 | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Locales/en-us/Pode.psd1 b/src/Locales/en-us/Pode.psd1 index 39175557b..f7a8bebbf 100644 --- a/src/Locales/en-us/Pode.psd1 +++ b/src/Locales/en-us/Pode.psd1 @@ -294,5 +294,4 @@ unsupportedStreamCompressionEncodingExceptionMessage = 'Unsupported stream compression encoding: {0}' LocalEndpointConflictExceptionMessage = "Both '{0}' and '{1}' are defined as local OpenAPI endpoints, but only one local endpoint is allowed per API definition." deprecatedFunctionWarningMessage = "WARNING: The function '{0}' is deprecated and will be removed in future releases. Please use the '{1}' function instead." - } \ No newline at end of file From 58e1d582616f8b73636b38c1e22e6e0154ee0102 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Mon, 21 Oct 2024 08:28:37 -0700 Subject: [PATCH 15/53] changed New-PodeCustomLoggingMethod to support only runspace --- examples/Logging.ps1 | 2 +- src/Public/Logging.ps1 | 121 ++++++++++++++++------------------------- 2 files changed, 49 insertions(+), 74 deletions(-) diff --git a/examples/Logging.ps1 b/examples/Logging.ps1 index 01703b94b..ab6e91ace 100644 --- a/examples/Logging.ps1 +++ b/examples/Logging.ps1 @@ -72,7 +72,7 @@ Start-PodeServer -browse { $rawItem | Out-File './examples/logs/customLegacy_rawItem.log' -Append } - $logging += New-PodeCustomLoggingMethod -UseRunspace -CustomOptions @{ 'opt1' = 'something'; 'opt2' = 'else' } -ScriptBlock { + $logging += New-PodeCustomLoggingMethod -CustomOptions @{ 'opt1' = 'something'; 'opt2' = 'else' } -ScriptBlock { $item | Out-File './examples/logs/customWithRunspace.log' -Append $options | Out-File './examples/logs/customWithRunspace_options.log' -Append $rawItem | Out-File './examples/logs/customWithRunspace_rawItem.log' -Append diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index e5f4fe94d..55239f68a 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -679,9 +679,6 @@ function New-PodeRestfulLoggingMethod { .PARAMETER ArgumentList An array of arguments to pass to the custom script block. -.PARAMETER UseRunspace - If set, the custom logging method will be executed in a separate runspace. - .PARAMETER CustomOptions A hashtable of custom options that will be passed to the script block when used inside a runspace. @@ -712,7 +709,7 @@ function New-PodeRestfulLoggingMethod { #> function New-PodeCustomLoggingMethod { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUSeDeclaredVarsMoreThanAssignments', '')] - [CmdletBinding(DefaultParameterSetName = 'RunSpace')] + [CmdletBinding(DefaultParameterSetName = 'DataFormat')] [OutputType([hashtable])] param( [Parameter(Mandatory = $true)] @@ -731,31 +728,20 @@ function New-PodeCustomLoggingMethod { [object[]] $ArgumentList, - [Parameter(ParameterSetName = 'RunSpace_DataFormat')] - [Parameter(ParameterSetName = 'RunSpace_ISO8601')] - [Parameter(ParameterSetName = 'RunSpace')] - [switch] - $UseRunspace, - [Parameter()] [hashtable] $CustomOptions = @{}, - - [Parameter(ParameterSetName = 'RunSpace_DataFormat')] - [Parameter(ParameterSetName = 'RunSpace_ISO8601')] + [Parameter()] [ValidateSet('Ignore', 'Report', 'Halt')] [string] $FailureAction = 'Ignore', - - [Parameter(ParameterSetName = 'RunSpace_DataFormat')] [Parameter(ParameterSetName = 'DataFormat')] [ValidateScript({ Test-PodeDateFormat $_ })] [string] $DataFormat, - [Parameter(ParameterSetName = 'RunSpace_ISO8601')] [Parameter(ParameterSetName = 'ISO8601')] [switch] $ISO8601, @@ -777,67 +763,49 @@ function New-PodeCustomLoggingMethod { } } - $methodId = New-PodeGuid - - if ($UseRunspace.IsPresent) { - # Create the script block for the custom logging method running in a separate runspace - $enanchedScriptBlock = { - param($MethodId) - - $log = @{} - while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { - Start-Sleep -Milliseconds 100 - - if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { - if ($null -ne $log) { - $Item = $log.item - $Options = $log.options - $RawItem = $log.rawItem - try { - # Original ScriptBlock Start - <# ScriptBlock #> - # Original ScriptBlock End - } - catch { - Invoke-PodeHandleFailure -Message "Custom Logging $MethodId Error. message: $_" -FailureAction $options.FailureAction - } + # Create the script block for the custom logging method running in a separate runspace + $enanchedScriptBlock = { + param($MethodId) + + $log = @{} + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + Start-Sleep -Milliseconds 100 + + if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { + if ($null -ne $log) { + $Item = $log.item + $Options = $log.options + $RawItem = $log.rawItem + try { + # Original ScriptBlock Start + <# ScriptBlock #> + # Original ScriptBlock End + } + catch { + Invoke-PodeHandleFailure -Message "Custom Logging $MethodId Error. message: $_" -FailureAction $options.FailureAction } } } } + } - # Register the enhanced script block in Pode's logging method - $PodeContext.Server.Logging.Method[$methodId] = @{ - ScriptBlock = [ScriptBlock]::Create($enanchedScriptBlock.ToString().Replace('<# ScriptBlock #>', $ScriptBlock.ToString())) - Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() - } + $methodId = New-PodeGuid - return @{ - Id = $methodId - Batch = New-PodeLogBatchInfo - Logger = @() - Arguments = @{ - FailureAction = $FailureAction - DataFormat = $DataFormat - AsUTC = $AsUTC - } + $CustomOptions - } + # Register the enhanced script block in Pode's logging method + $PodeContext.Server.Logging.Method[$methodId] = @{ + ScriptBlock = [ScriptBlock]::Create($enanchedScriptBlock.ToString().Replace('<# ScriptBlock #>', $ScriptBlock.ToString())) + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() } - else { - # Convert scoped variables for the script block if not using a runspace - $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState - return @{ - Id = $methodId - ScriptBlock = $ScriptBlock - UsingVariables = $usingVars - Batch = New-PodeLogBatchInfo - Logger = @() - Arguments = $ArgumentList - DataFormat = $DataFormat - AsUTC = $AsUTC - NoRunspace = $true - } + return @{ + Id = $methodId + Batch = New-PodeLogBatchInfo + Logger = @() + Arguments = @{ + FailureAction = $FailureAction + DataFormat = $DataFormat + AsUTC = $AsUTC + } + $CustomOptions } } @@ -1042,11 +1010,18 @@ function New-PodeLoggingMethod { # WARNING: Function `New-PodeLoggingMethod` is deprecated. Please use '{0}' function instead. Write-PodeHost ($PodeLocale.deprecatedFunctionWarningMessage -f 'New-PodeLoggingMethod', 'New-PodeCustomLoggingMethod') -ForegroundColor Yellow - $customParams = @{ - ScriptBlock = $PSBoundParameters['ScriptBlock'] - ArgumentList = $PSBoundParameters['ArgumentList'] + # Convert scoped variables for the script block if not using a runspace + $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState + + return @{ + Id = New-PodeGuid + ScriptBlock = $ScriptBlock + UsingVariables = $usingVars + Batch = New-PodeLogBatchInfo + Logger = @() + Arguments = $ArgumentList + NoRunspace = $true } - return New-PodeCustomLoggingMethod @customParams } } } From 078737ab798ca8d7086f3d652f7eeb82c72b9aae Mon Sep 17 00:00:00 2001 From: mdaneri Date: Mon, 21 Oct 2024 18:53:36 -0700 Subject: [PATCH 16/53] Update Context.ps1 --- src/Private/Context.ps1 | 56 +++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/Private/Context.ps1 b/src/Private/Context.ps1 index 523f06e9d..8887b2779 100644 --- a/src/Private/Context.ps1 +++ b/src/Private/Context.ps1 @@ -117,22 +117,23 @@ function New-PodeContext { } # basic context object - $ctx = [PSCustomObject]::new() | - Add-Member -MemberType NoteProperty -Name Threads -Value @{} -PassThru | - Add-Member -MemberType NoteProperty -Name Timers -Value @{} -PassThru | - Add-Member -MemberType NoteProperty -Name Schedules -Value @{} -PassThru | - Add-Member -MemberType NoteProperty -Name Tasks -Value @{} -PassThru | - Add-Member -MemberType NoteProperty -Name RunspacePools -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name Runspaces -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name RunspaceState -Value $null -PassThru | - Add-Member -MemberType NoteProperty -Name Tokens -Value @{} -PassThru | - Add-Member -MemberType NoteProperty -Name Threading -Value @{} -PassThru | - Add-Member -MemberType NoteProperty -Name Server -Value @{} -PassThru | - Add-Member -MemberType NoteProperty -Name Metrics -Value @{} -PassThru | - Add-Member -MemberType NoteProperty -Name Listeners -Value @() -PassThru | - Add-Member -MemberType NoteProperty -Name Receivers -Value @() -PassThru | - Add-Member -MemberType NoteProperty -Name Watchers -Value @() -PassThru | - Add-Member -MemberType NoteProperty -Name Fim -Value @{} -PassThru + $ctx = [PSCustomObject]@{ + Threads = @{} + Timers = @{} + Schedules = @{} + Tasks = @{} + RunspacePools = $null + Runspaces = $null + RunspaceState = $null + Tokens = @{} + Threading = @{} + Server = @{} + Metrics = @{} + Listeners = @() + Receivers = @() + Watchers = @() + Fim = @{} + } # set the server name, logic and root, and other basic properties $ctx.Server.Name = $Name @@ -843,17 +844,18 @@ function New-PodeStateContext { $Context ) - return ([PSCustomObject]::new() | - Add-Member -MemberType NoteProperty -Name Threads -Value $Context.Threads -PassThru | - Add-Member -MemberType NoteProperty -Name Timers -Value $Context.Timers -PassThru | - Add-Member -MemberType NoteProperty -Name Schedules -Value $Context.Schedules -PassThru | - Add-Member -MemberType NoteProperty -Name Tasks -Value $Context.Tasks -PassThru | - Add-Member -MemberType NoteProperty -Name Fim -Value $Context.Fim -PassThru | - Add-Member -MemberType NoteProperty -Name RunspacePools -Value $Context.RunspacePools -PassThru | - Add-Member -MemberType NoteProperty -Name Tokens -Value $Context.Tokens -PassThru | - Add-Member -MemberType NoteProperty -Name Metrics -Value $Context.Metrics -PassThru | - Add-Member -MemberType NoteProperty -Name Threading -Value $Context.Threading -PassThru | - Add-Member -MemberType NoteProperty -Name Server -Value $Context.Server -PassThru) + return [PSCustomObject]@{ + Threads = $Context.Threads + Timers = $Context.Timers + Schedules = $Context.Schedules + Tasks = $Context.Tasks + Fim = $Context.Fim + RunspacePools = $Context.RunspacePools + Tokens = $Context.Tokens + Metrics = $Context.Metrics + Threading = $Context.Threading + Server = $Context.Server + } } function Open-PodeConfiguration { From 5a77d59f8e37e7152db3e7d93baa1acc756ced2c Mon Sep 17 00:00:00 2001 From: mdaneri Date: Fri, 25 Oct 2024 17:18:13 -0700 Subject: [PATCH 17/53] revert PodetraceLog --- src/Public/Access.ps1 | 15 ----------- src/Public/Authentication.ps1 | 27 ------------------- src/Public/Caching.ps1 | 23 +--------------- src/Public/Core.ps1 | 21 +++++---------- src/Public/EndWare.ps1 | 3 --- src/Public/Events.ps1 | 3 --- src/Public/FileWatchers.ps1 | 9 ------- src/Public/Flash.ps1 | 9 ------- src/Public/Handlers.ps1 | 9 ------- src/Public/Headers.ps1 | 3 --- src/Public/Middleware.ps1 | 21 --------------- src/Public/OAComponents.ps1 | 29 +------------------- src/Public/OpenApi.ps1 | 33 ----------------------- src/Public/Responses.ps1 | 6 ----- src/Public/Routes.ps1 | 42 ----------------------------- src/Public/SSE.ps1 | 6 ----- src/Public/Schedules.ps1 | 12 --------- src/Public/ScopedVariables.ps1 | 10 ------- src/Public/Secrets.ps1 | 3 --- src/Public/Security.ps1 | 9 ------- src/Public/Sessions.ps1 | 3 --- src/Public/State.ps1 | 5 ---- src/Public/Tasks.ps1 | 13 --------- src/Public/Threading.ps1 | 18 ------------- src/Public/Timers.ps1 | 9 ------- src/Public/Utilities.ps1 | 2 -- src/Public/Verbs.ps1 | 9 ------- src/Public/WebSockets.ps1 | 6 ----- tests/unit/Authentication.Tests.ps1 | 2 -- tests/unit/Context.Tests.ps1 | 2 -- tests/unit/Cookies.Tests.ps1 | 2 -- tests/unit/CronParser.Tests.ps1 | 2 -- tests/unit/Cryptography.Tests.ps1 | 2 -- tests/unit/Endware.Tests.ps1 | 2 -- tests/unit/Flash.Tests.ps1 | 2 -- tests/unit/Handlers.Tests.ps1 | 2 -- tests/unit/Headers.Tests.ps1 | 2 -- tests/unit/Helpers.Tests.ps1 | 2 -- tests/unit/Mappers.Tests.ps1 | 2 -- tests/unit/Metrics.Tests.ps1 | 2 -- tests/unit/Middleware.Tests.ps1 | 2 -- tests/unit/NameGenerator.Tests.ps1 | 2 -- tests/unit/OpenApi.Tests.ps1 | 2 -- tests/unit/PrivateOpenApi.Tests.ps1 | 2 -- tests/unit/Responses.Tests.ps1 | 2 -- tests/unit/Routes.Tests.ps1 | 2 -- tests/unit/Schedules.Tests.ps1 | 4 +-- tests/unit/Security.Tests.ps1 | 3 --- tests/unit/Server.Tests.ps1 | 2 -- tests/unit/Serverless.Tests.ps1 | 2 -- tests/unit/Sessions.Tests.ps1 | 2 -- tests/unit/State.Tests.ps1 | 2 -- tests/unit/Timers.Tests.ps1 | 4 +-- 53 files changed, 10 insertions(+), 403 deletions(-) diff --git a/src/Public/Access.ps1 b/src/Public/Access.ps1 index b56fe8f78..ebf1cb3b5 100644 --- a/src/Public/Access.ps1 +++ b/src/Public/Access.ps1 @@ -173,9 +173,6 @@ function Add-PodeAccess { $Match = 'One' ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $pipelineItemCount = 0 } @@ -320,9 +317,6 @@ function Add-PodeAccessCustom { ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $routes = @() } @@ -624,9 +618,6 @@ function Remove-PodeAccess { $Name ) process { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $null = $PodeContext.Server.Authorisations.Methods.Remove($Name) } } @@ -645,9 +636,6 @@ function Clear-PodeAccess { [CmdletBinding()] param() - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $PodeContext.Server.Authorisations.Methods.Clear() } @@ -689,9 +677,6 @@ function Add-PodeAccessMiddleware { $Route ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - if (!(Test-PodeAccessExists -Name $Access)) { throw ($PodeLocale.accessMethodNotExistExceptionMessage -f $Access) #"Access method does not exist: $($Access)" } diff --git a/src/Public/Authentication.ps1 b/src/Public/Authentication.ps1 index 26d45b351..64d6ea2df 100644 --- a/src/Public/Authentication.ps1 +++ b/src/Public/Authentication.ps1 @@ -781,9 +781,6 @@ function Add-PodeAuth { $SuccessUseOrigin ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $pipelineItemCount = 0 } @@ -1321,9 +1318,6 @@ function Add-PodeAuthWindowsAd { $KeepCredential ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $pipelineItemCount = 0 } @@ -1477,9 +1471,6 @@ function Add-PodeAuthSession { $SuccessUseOrigin ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # if sessions haven't been setup, error if (!(Test-PodeSessionsEnabled)) { # Sessions have not been configured @@ -1573,9 +1564,6 @@ function Remove-PodeAuth { $Name ) process { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $null = $PodeContext.Server.Authentications.Methods.Remove($Name) } } @@ -1594,9 +1582,6 @@ function Clear-PodeAuth { [CmdletBinding()] param() - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $PodeContext.Server.Authentications.Methods.Clear() } @@ -1647,9 +1632,6 @@ function Add-PodeAuthMiddleware { $OADefinitionTag ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $DefinitionTag = Test-PodeOADefinitionTag -Tag $OADefinitionTag if (!(Test-PodeAuthExists -Name $Authentication)) { @@ -1777,9 +1759,6 @@ function Add-PodeAuthIIS { $SuccessUseOrigin ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # ensure we're on Windows! if (!(Test-PodeIsWindows)) { # IIS Authentication support is for Windows only @@ -1945,9 +1924,6 @@ function Add-PodeAuthUserFile { $SuccessUseOrigin ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $pipelineItemCount = 0 } @@ -2123,9 +2099,6 @@ function Add-PodeAuthWindowsLocal { $SuccessUseOrigin ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $pipelineItemCount = 0 } diff --git a/src/Public/Caching.ps1 b/src/Public/Caching.ps1 index 2f660b890..f40892efc 100644 --- a/src/Public/Caching.ps1 +++ b/src/Public/Caching.ps1 @@ -119,9 +119,6 @@ function Set-PodeCache { ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # Initialize an array to hold piped-in values $pipelineValue = @() } @@ -247,9 +244,6 @@ function Remove-PodeCache { $Storage = $null ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # inmem or custom storage? if ([string]::IsNullOrEmpty($Storage)) { $Storage = $PodeContext.Server.Cache.DefaultStorage @@ -296,9 +290,6 @@ function Clear-PodeCache { $Storage = $null ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # inmem or custom storage? if ([string]::IsNullOrEmpty($Storage)) { $Storage = $PodeContext.Server.Cache.DefaultStorage @@ -384,9 +375,6 @@ function Add-PodeCacheStorage { $Default ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # test if storage already exists if (Test-PodeCacheStorage -Name $Name) { # Cache Storage with name already exists @@ -431,9 +419,6 @@ function Remove-PodeCacheStorage { $Name ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $null = $PodeContext.Server.Cache.Storage.Remove($Name) } @@ -506,9 +491,6 @@ function Set-PodeCacheDefaultStorage { $Name ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $PodeContext.Server.Cache.DefaultStorage = $Name } @@ -550,9 +532,6 @@ function Set-PodeCacheDefaultTtl { $Value ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - if ($Value -le 0) { return } @@ -575,4 +554,4 @@ function Get-PodeCacheDefaultTtl { param() return $PodeContext.Server.Cache.DefaultTtl -} +} \ No newline at end of file diff --git a/src/Public/Core.ps1 b/src/Public/Core.ps1 index 54ce8898f..c8921f283 100644 --- a/src/Public/Core.ps1 +++ b/src/Public/Core.ps1 @@ -146,9 +146,6 @@ function Start-PodeServer { # Sets the name of the current runspace Set-PodeCurrentRunspaceName -Name 'PodeServer' - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # ensure the session is clean $PodeContext = $null $ShowDoneMessage = $true @@ -194,11 +191,12 @@ function Start-PodeServer { [Console]::TreatControlCAsInput = $true } - if ($PodeContext.Server.Logging.Enabled) { - Enable-PodeLogging - } - # start the file monitor for interally restarting - Start-PodeFileMonitor + if ($PodeContext.Server.Logging.Enabled) { + Enable-PodeLogging + } + + # start the file monitor for interally restarting + Start-PodeFileMonitor # start the server Start-PodeInternalServer -Request $Request -Browse:$Browse @@ -941,9 +939,6 @@ function Add-PodeEndpoint { $Default ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # error if serverless Test-PodeIsServerless -FunctionName 'Add-PodeEndpoint' -ThrowError @@ -1364,10 +1359,6 @@ function Set-PodeDefaultFolder { [string] $Path ) - - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - if (Test-Path -Path $Path -PathType Container) { $PodeContext.Server.DefaultFolders[$Type] = $Path } diff --git a/src/Public/EndWare.ps1 b/src/Public/EndWare.ps1 index f9b5418bb..191f22a2b 100644 --- a/src/Public/EndWare.ps1 +++ b/src/Public/EndWare.ps1 @@ -26,9 +26,6 @@ function Add-PodeEndware { $ArgumentList ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # check for scoped vars $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState diff --git a/src/Public/Events.ps1 b/src/Public/Events.ps1 index a31f214aa..b3f800a0d 100644 --- a/src/Public/Events.ps1 +++ b/src/Public/Events.ps1 @@ -182,9 +182,6 @@ function Clear-PodeEvent { $Type ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $null = $PodeContext.Server.Events[$Type].Clear() } diff --git a/src/Public/FileWatchers.ps1 b/src/Public/FileWatchers.ps1 index 257e23f3a..67c8468d7 100644 --- a/src/Public/FileWatchers.ps1 +++ b/src/Public/FileWatchers.ps1 @@ -106,9 +106,6 @@ function Add-PodeFileWatcher { $PassThru ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # set random name if ([string]::IsNullOrEmpty($Name)) { $Name = New-PodeGuid -Secure @@ -281,9 +278,6 @@ function Remove-PodeFileWatcher { $Name ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $null = $PodeContext.Fim.Items.Remove($Name) } @@ -302,9 +296,6 @@ function Clear-PodeFileWatchers { [CmdletBinding()] param() - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $PodeContext.Fim.Items.Clear() } diff --git a/src/Public/Flash.ps1 b/src/Public/Flash.ps1 index b2a008949..0c9364d70 100644 --- a/src/Public/Flash.ps1 +++ b/src/Public/Flash.ps1 @@ -27,9 +27,6 @@ function Add-PodeFlashMessage { $Message ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # if sessions haven't been setup, error if (!(Test-PodeSessionsEnabled)) { # Sessions are required to use Flash messages @@ -64,9 +61,6 @@ function Clear-PodeFlashMessages { [CmdletBinding()] param() - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # if sessions haven't been setup, error if (!(Test-PodeSessionsEnabled)) { # Sessions are required to use Flash messages @@ -174,9 +168,6 @@ function Remove-PodeFlashMessage { $Name ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # if sessions haven't been setup, error if (!(Test-PodeSessionsEnabled)) { # Sessions are required to use Flash messages diff --git a/src/Public/Handlers.ps1 b/src/Public/Handlers.ps1 index f5ed34605..244570397 100644 --- a/src/Public/Handlers.ps1 +++ b/src/Public/Handlers.ps1 @@ -54,9 +54,6 @@ function Add-PodeHandler { $ArgumentList ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # error if serverless Test-PodeIsServerless -FunctionName 'Add-PodeHandler' -ThrowError @@ -112,9 +109,6 @@ function Remove-PodeHandler { $Name ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # ensure handler does exist if (!$PodeContext.Server.Handlers[$Type].ContainsKey($Name)) { return @@ -147,9 +141,6 @@ function Clear-PodeHandlers { $Type ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - if (![string]::IsNullOrWhiteSpace($Type)) { $PodeContext.Server.Handlers[$Type].Clear() } diff --git a/src/Public/Headers.ps1 b/src/Public/Headers.ps1 index 660d20026..84a907b2f 100644 --- a/src/Public/Headers.ps1 +++ b/src/Public/Headers.ps1 @@ -87,9 +87,6 @@ function Add-PodeHeaderBulk { $Strict ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - foreach ($key in $Values.Keys) { $value = $Values[$key] diff --git a/src/Public/Middleware.ps1 b/src/Public/Middleware.ps1 index 319019f51..8b7f8d15d 100644 --- a/src/Public/Middleware.ps1 +++ b/src/Public/Middleware.ps1 @@ -38,9 +38,6 @@ function Add-PodeAccessRule { $Values ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # error if serverless Test-PodeIsServerless -FunctionName 'Add-PodeAccessRule' -ThrowError @@ -109,9 +106,6 @@ function Add-PodeLimitRule { $Group ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # call the appropriate limit method foreach ($value in $Values) { switch ($Type.ToLowerInvariant()) { @@ -366,9 +360,6 @@ function Add-PodeBodyParser { $ScriptBlock ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $pipelineItemCount = 0 } @@ -419,8 +410,6 @@ function Remove-PodeBodyParser { ) process { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters # if there's no parser for the type, return if (!$PodeContext.Server.BodyParsers.ContainsKey($ContentType)) { return @@ -485,9 +474,6 @@ function Add-PodeMiddleware { $ArgumentList ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $pipelineItemCount = 0 } @@ -499,7 +485,6 @@ function Add-PodeMiddleware { if ($pipelineItemCount -gt 1) { throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name)) } - # ensure name doesn't already exist if (($PodeContext.Server.Middleware | Where-Object { $_.Name -ieq $Name } | Measure-Object).Count -gt 0) { # [Middleware] Name: Middleware already defined @@ -614,9 +599,6 @@ function Remove-PodeMiddleware { $Name ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $PodeContext.Server.Middleware = @($PodeContext.Server.Middleware | Where-Object { $_.Name -ine $Name }) } @@ -634,9 +616,6 @@ function Clear-PodeMiddleware { [CmdletBinding()] param() - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $PodeContext.Server.Middleware = @() } diff --git a/src/Public/OAComponents.ps1 b/src/Public/OAComponents.ps1 index 6378b0b0c..e65b18912 100644 --- a/src/Public/OAComponents.ps1 +++ b/src/Public/OAComponents.ps1 @@ -78,12 +78,7 @@ function Add-PodeOAComponentResponse { [string[]] $DefinitionTag ) - - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag - foreach ($tag in $DefinitionTag) { $PodeContext.Server.OpenAPI.Definitions[$tag].components.responses[$Name] = New-PodeOResponseInternal -DefinitionTag $tag -Params $PSBoundParameters } @@ -146,9 +141,6 @@ function Add-PodeOAComponentSchema { $DefinitionTag ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $pipelineItemCount = 0 } @@ -246,9 +238,6 @@ function Add-PodeOAComponentHeader { $DefinitionTag ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $pipelineItemCount = 0 } @@ -344,9 +333,6 @@ function Add-PodeOAComponentRequestBody { $DefinitionTag ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $pipelineItemCount = 0 } @@ -432,9 +418,6 @@ function Add-PodeOAComponentParameter { $DefinitionTag ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $pipelineItemCount = 0 } @@ -526,12 +509,7 @@ function Add-PodeOAComponentExample { [string[]] $DefinitionTag ) - - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag - foreach ($tag in $DefinitionTag) { $Example = [ordered]@{ } if ($Summary) { @@ -966,12 +944,7 @@ function Remove-PodeOAComponent { [string[]] $DefinitionTag ) - - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag - foreach ($tag in $DefinitionTag) { if (!($PodeContext.Server.OpenAPI.Definitions[$tag].components[$field ].keys -ccontains $Name)) { $PodeContext.Server.OpenAPI.Definitions[$tag].components[$field ].remove($Name) @@ -989,4 +962,4 @@ if (!(Test-Path Alias:Enable-PodeOA)) { if (!(Test-Path Alias:Get-PodeOpenApiDefinition)) { New-Alias Get-PodeOpenApiDefinition -Value Get-PodeOADefinition -} +} \ No newline at end of file diff --git a/src/Public/OpenApi.ps1 b/src/Public/OpenApi.ps1 index aceca64c9..c7eaa348a 100644 --- a/src/Public/OpenApi.ps1 +++ b/src/Public/OpenApi.ps1 @@ -395,9 +395,6 @@ function Add-PodeOAServerEndpoint { $DefinitionTag ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # If the DefinitionTag is empty, use the selected tag from Pode's OpenAPI context if (Test-PodeIsEmpty -Value $DefinitionTag) { @@ -653,9 +650,6 @@ function Add-PodeOAResponse { $DefinitionTag ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # Initialize an array to hold piped-in values $pipelineValue = @() } @@ -743,9 +737,6 @@ function Remove-PodeOAResponse { $PassThru ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # Initialize an array to hold piped-in values $pipelineValue = @() } @@ -829,9 +820,6 @@ function Set-PodeOARequest { $DefinitionTag ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # Initialize an array to hold piped-in values $pipelineValue = @() } @@ -1665,9 +1653,6 @@ function Set-PodeOARouteInfo { $DefinitionTag ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # Initialize an array to hold piped-in values $pipelineValue = @() } @@ -2125,9 +2110,6 @@ function Add-PodeOAExternalDoc { $DefinitionTag ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $pipelineItemCount = 0 } @@ -2199,9 +2181,6 @@ function Add-PodeOATag { $DefinitionTag ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag foreach ($tag in $DefinitionTag) { @@ -2308,9 +2287,6 @@ function Add-PodeOAInfo { $DefinitionTag ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $DefinitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag $Info = [ordered]@{} @@ -2763,9 +2739,6 @@ function Add-PodeOACallBack { $DefinitionTag ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # Initialize an array to hold piped-in values $pipelineValue = @() } @@ -3365,9 +3338,6 @@ function Add-PodeOAExternalRoute { $DefinitionTag ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # Initialize an array to hold piped-in values $pipelineValue = @() } @@ -3550,9 +3520,6 @@ function Add-PodeOAWebhook { $DefinitionTag ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $_definitionTag = Test-PodeOADefinitionTag -Tag $DefinitionTag $refRoute = @{ diff --git a/src/Public/Responses.ps1 b/src/Public/Responses.ps1 index edc5a9379..9a9f82e3c 100644 --- a/src/Public/Responses.ps1 +++ b/src/Public/Responses.ps1 @@ -1611,9 +1611,6 @@ function Set-PodeViewEngine { $Extension ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # truncate markdown if ($Type -ieq 'Markdown') { $Type = 'md' @@ -1855,9 +1852,6 @@ function Add-PodeViewFolder { $Source ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # ensure the folder doesn't already exist if ($PodeContext.Server.Views.ContainsKey($Name)) { # The Views folder name already exists diff --git a/src/Public/Routes.ps1 b/src/Public/Routes.ps1 index a3e3d42bf..9bc6db522 100644 --- a/src/Public/Routes.ps1 +++ b/src/Public/Routes.ps1 @@ -208,9 +208,6 @@ function Add-PodeRoute { $OADefinitionTag ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # check if we have any route group info defined if ($null -ne $RouteGroup) { if (![string]::IsNullOrWhiteSpace($RouteGroup.Path)) { @@ -653,9 +650,6 @@ function Add-PodeStaticRoute { $RedirectToDefault ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # check if we have any route group info defined if ($null -ne $RouteGroup) { if (![string]::IsNullOrWhiteSpace($RouteGroup.Path)) { @@ -970,9 +964,6 @@ function Add-PodeSignalRoute { $IfExists = 'Default' ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # check if we have any route group info defined if ($null -ne $RouteGroup) { if (![string]::IsNullOrWhiteSpace($RouteGroup.Path)) { @@ -1194,9 +1185,6 @@ function Add-PodeRouteGroup { $OADefinitionTag ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - if (Test-PodeIsEmpty $Routes) { # The Route parameter needs a valid, not empty, scriptblock throw ($PodeLocale.routeParameterNeedsValidScriptblockExceptionMessage) @@ -1455,9 +1443,6 @@ function Add-PodeStaticRouteGroup { $RedirectToDefault ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - if (Test-PodeIsEmpty $Routes) { # The Route parameter needs a valid, not empty, scriptblock throw ($PodeLocale.routeParameterNeedsValidScriptblockExceptionMessage) @@ -1626,9 +1611,6 @@ function Add-PodeSignalRouteGroup { $IfExists = 'Default' ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - if (Test-PodeIsEmpty $Routes) { # The Route parameter needs a valid, not empty, scriptblock throw ($PodeLocale.routeParameterNeedsValidScriptblockExceptionMessage) @@ -1705,9 +1687,6 @@ function Remove-PodeRoute { $EndpointName ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # split route on '?' for query $Path = Split-PodeRouteQuery -Path $Path @@ -1775,9 +1754,6 @@ function Remove-PodeStaticRoute { $EndpointName ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $Method = 'Static' # ensure the route has appropriate slashes and replace parameters @@ -1827,9 +1803,6 @@ function Remove-PodeSignalRoute { $EndpointName ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $Method = 'Signal' # ensure the route has appropriate slashes and replace parameters @@ -1877,9 +1850,6 @@ function Clear-PodeRoutes { $Method ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - if (![string]::IsNullOrWhiteSpace($Method)) { $PodeContext.Server.Routes[$Method].Clear() } @@ -1905,9 +1875,6 @@ function Clear-PodeStaticRoutes { [CmdletBinding()] param() - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $PodeContext.Server.Routes['Static'].Clear() } @@ -1926,9 +1893,6 @@ function Clear-PodeSignalRoutes { [CmdletBinding()] param() - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $PodeContext.Server.Routes['Signal'].Clear() } @@ -2323,9 +2287,6 @@ function Add-PodePage { $FlashMessages ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $logic = $null $arg = $null @@ -2688,9 +2649,6 @@ function Set-PodeRouteIfExistsPreference { $Value = 'Default' ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $PodeContext.Server.Preferences.Routes.IfExists = $Value } diff --git a/src/Public/SSE.ps1 b/src/Public/SSE.ps1 index 574af6bd6..c1d620139 100644 --- a/src/Public/SSE.ps1 +++ b/src/Public/SSE.ps1 @@ -130,9 +130,6 @@ function Set-PodeSseDefaultScope { $Scope ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $PodeContext.Server.Sse.DefaultScope = $Scope } @@ -614,9 +611,6 @@ function Set-PodeSseBroadcastLevel { $Type ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $PodeContext.Server.Sse.BroadcastLevel[$Name] = $Type.ToLowerInvariant() } diff --git a/src/Public/Schedules.ps1 b/src/Public/Schedules.ps1 index 8a3a58b2e..974a0341c 100644 --- a/src/Public/Schedules.ps1 +++ b/src/Public/Schedules.ps1 @@ -98,9 +98,6 @@ function Add-PodeSchedule { $OnStart ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # error if serverless Test-PodeIsServerless -FunctionName 'Add-PodeSchedule' -ThrowError @@ -183,9 +180,6 @@ function Set-PodeScheduleConcurrency { $Maximum ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # error if <=0 if ($Maximum -le 0) { # Maximum concurrent schedules must be >=1 but got @@ -269,9 +263,6 @@ function Remove-PodeSchedule { $Name ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $null = $PodeContext.Schedules.Items.Remove($Name) } @@ -290,9 +281,6 @@ function Clear-PodeSchedules { [CmdletBinding()] param() - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $PodeContext.Schedules.Items.Clear() } diff --git a/src/Public/ScopedVariables.ps1 b/src/Public/ScopedVariables.ps1 index 05dcf6c06..a21805c13 100644 --- a/src/Public/ScopedVariables.ps1 +++ b/src/Public/ScopedVariables.ps1 @@ -240,9 +240,6 @@ function Add-PodeScopedVariable { $ScriptBlock ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - Add-PodeScopedVariableInternal @PSBoundParameters } @@ -267,9 +264,6 @@ function Remove-PodeScopedVariable { $Name ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $null = $PodeContext.Server.ScopedVariables.Remove($Name) } @@ -312,10 +306,6 @@ function Clear-PodeScopedVariables { [CmdletBinding()] param() - - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $null = $PodeContext.Server.ScopedVariables.Clear() } diff --git a/src/Public/Secrets.ps1 b/src/Public/Secrets.ps1 index fc4cd17c8..782f659cf 100644 --- a/src/Public/Secrets.ps1 +++ b/src/Public/Secrets.ps1 @@ -758,9 +758,6 @@ function Remove-PodeSecret { $ArgumentList ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # has the vault been registered? if (!(Test-PodeSecretVault -Name $Vault)) { # No Secret Vault with the name has been registered diff --git a/src/Public/Security.ps1 b/src/Public/Security.ps1 index 1ac241dba..f53fcb2ca 100644 --- a/src/Public/Security.ps1 +++ b/src/Public/Security.ps1 @@ -138,9 +138,6 @@ function Add-PodeSecurityHeader { $Append ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - if ([string]::IsNullOrWhiteSpace($Value)) { return } @@ -181,9 +178,6 @@ function Remove-PodeSecurityHeader { $Name ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $PodeContext.Server.Security.Headers.Remove($Name) } @@ -654,9 +648,6 @@ function Add-PodeSecurityContentSecurityPolicy { $ReportOnly ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - Set-PodeSecurityContentSecurityPolicyInternal -Params $PSBoundParameters -Append } diff --git a/src/Public/Sessions.ps1 b/src/Public/Sessions.ps1 index 43566fdd2..5ffbbb80c 100644 --- a/src/Public/Sessions.ps1 +++ b/src/Public/Sessions.ps1 @@ -180,9 +180,6 @@ function Remove-PodeSession { [CmdletBinding()] param() - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # if sessions haven't been setup, error if (!(Test-PodeSessionsEnabled)) { # The sessions have not been configured diff --git a/src/Public/State.ps1 b/src/Public/State.ps1 index fc98bbdc4..1c841e586 100644 --- a/src/Public/State.ps1 +++ b/src/Public/State.ps1 @@ -38,8 +38,6 @@ function Set-PodeState { ) begin { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters if ($null -eq $PodeContext.Server.State) { # Pode has not been initialized throw ($PodeLocale.podeNotInitializedExceptionMessage) @@ -198,9 +196,6 @@ function Remove-PodeState { $Name ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - if ($null -eq $PodeContext.Server.State) { # Pode has not been initialized throw ($PodeLocale.podeNotInitializedExceptionMessage) diff --git a/src/Public/Tasks.ps1 b/src/Public/Tasks.ps1 index 8d9075057..c0267f9be 100644 --- a/src/Public/Tasks.ps1 +++ b/src/Public/Tasks.ps1 @@ -57,10 +57,6 @@ function Add-PodeTask { [string] $TimeoutFrom = 'Create' ) - - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # ensure the task doesn't already exist if ($PodeContext.Tasks.Items.ContainsKey($Name)) { # [Task] Task already defined @@ -110,9 +106,6 @@ function Set-PodeTaskConcurrency { $Maximum ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # error if <=0 if ($Maximum -le 0) { # Maximum concurrent tasks must be >=1 but got @@ -237,9 +230,6 @@ function Remove-PodeTask { $Name ) process { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $null = $PodeContext.Tasks.Items.Remove($Name) } } @@ -259,9 +249,6 @@ function Clear-PodeTasks { [CmdletBinding()] param() - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $PodeContext.Tasks.Items.Clear() } diff --git a/src/Public/Threading.ps1 b/src/Public/Threading.ps1 index fb1c17903..c192c063f 100644 --- a/src/Public/Threading.ps1 +++ b/src/Public/Threading.ps1 @@ -149,9 +149,6 @@ function Remove-PodeLockable { $Name ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - if (Test-PodeLockable -Name $Name) { $PodeContext.Threading.Lockables.Custom.Remove($Name) } @@ -383,9 +380,6 @@ function Clear-PodeLockables { [CmdletBinding()] param() - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - if (Test-PodeIsEmpty $PodeContext.Threading.Lockables.Custom) { return } @@ -526,9 +520,6 @@ function Remove-PodeMutex { $Name ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - if (Test-PodeMutex -Name $Name) { $PodeContext.Threading.Mutexes[$Name].Dispose() $PodeContext.Threading.Mutexes.Remove($Name) @@ -681,9 +672,6 @@ function Clear-PodeMutexes { [CmdletBinding()] param() - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - if (Test-PodeIsEmpty $PodeContext.Threading.Mutexes) { return } @@ -835,9 +823,6 @@ function Remove-PodeSemaphore { $Name ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - if (Test-PodeSemaphore -Name $Name) { $PodeContext.Threading.Semaphores[$Name].Dispose() $PodeContext.Threading.Semaphores.Remove($Name) @@ -1001,9 +986,6 @@ function Clear-PodeSemaphores { [CmdletBinding()] param() - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - if (Test-PodeIsEmpty $PodeContext.Threading.Semaphores) { return } diff --git a/src/Public/Timers.ps1 b/src/Public/Timers.ps1 index 576010c31..78a9ecc73 100644 --- a/src/Public/Timers.ps1 +++ b/src/Public/Timers.ps1 @@ -76,9 +76,6 @@ function Add-PodeTimer { $OnStart ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # error if serverless Test-PodeIsServerless -FunctionName 'Add-PodeTimer' -ThrowError @@ -199,9 +196,6 @@ function Remove-PodeTimer { $Name ) process { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $null = $PodeContext.Timers.Items.Remove($Name) } } @@ -221,9 +215,6 @@ function Clear-PodeTimers { [CmdletBinding()] param() - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $PodeContext.Timers.Items.Clear() } diff --git a/src/Public/Utilities.ps1 b/src/Public/Utilities.ps1 index 1c32a379e..ed4ecdec6 100644 --- a/src/Public/Utilities.ps1 +++ b/src/Public/Utilities.ps1 @@ -100,8 +100,6 @@ function Start-PodeStopwatch { ) begin { $pipelineItemCount = 0 - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters } process { diff --git a/src/Public/Verbs.ps1 b/src/Public/Verbs.ps1 index 66dbb522a..3adf26557 100644 --- a/src/Public/Verbs.ps1 +++ b/src/Public/Verbs.ps1 @@ -69,9 +69,6 @@ function Add-PodeVerb { $Close ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # find placeholder parameters in verb (ie: COMMAND :parameter) $Verb = Resolve-PodePlaceholder -Path $Verb @@ -149,9 +146,6 @@ function Remove-PodeVerb { $EndpointName ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # ensure the verb placeholders are replaced $Verb = Resolve-PodePlaceholder -Path $Verb @@ -186,9 +180,6 @@ function Clear-PodeVerbs { [CmdletBinding()] param() - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $PodeContext.Server.Verbs.Clear() } diff --git a/src/Public/WebSockets.ps1 b/src/Public/WebSockets.ps1 index 5c92bb593..e1d9f4958 100644 --- a/src/Public/WebSockets.ps1 +++ b/src/Public/WebSockets.ps1 @@ -21,9 +21,6 @@ function Set-PodeWebSocketConcurrency { $Maximum ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # error if <=0 if ($Maximum -le 0) { # Maximum concurrent WebSocket threads must be >=1 but got @@ -208,9 +205,6 @@ function Remove-PodeWebSocket { $Name ) - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - if ([string]::IsNullOrWhiteSpace($Name) -and ($null -ne $WsEvent)) { $Name = $WsEvent.Request.WebSocket.Name } diff --git a/tests/unit/Authentication.Tests.ps1 b/tests/unit/Authentication.Tests.ps1 index d27bdbfbd..18fc79b4c 100644 --- a/tests/unit/Authentication.Tests.ps1 +++ b/tests/unit/Authentication.Tests.ps1 @@ -6,8 +6,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} } $now = [datetime]::UtcNow diff --git a/tests/unit/Context.Tests.ps1 b/tests/unit/Context.Tests.ps1 index be6879892..f9185885f 100644 --- a/tests/unit/Context.Tests.ps1 +++ b/tests/unit/Context.Tests.ps1 @@ -6,8 +6,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} $PodeContext = @{ 'Server' = $null; } } diff --git a/tests/unit/Cookies.Tests.ps1 b/tests/unit/Cookies.Tests.ps1 index 5e4e6a55c..070ea84c4 100644 --- a/tests/unit/Cookies.Tests.ps1 +++ b/tests/unit/Cookies.Tests.ps1 @@ -6,8 +6,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} } Describe 'Test-PodeCookie' { It 'Returns true' { diff --git a/tests/unit/CronParser.Tests.ps1 b/tests/unit/CronParser.Tests.ps1 index bc79c670e..42cb24332 100644 --- a/tests/unit/CronParser.Tests.ps1 +++ b/tests/unit/CronParser.Tests.ps1 @@ -6,8 +6,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} } Describe 'Get-PodeCronField' { diff --git a/tests/unit/Cryptography.Tests.ps1 b/tests/unit/Cryptography.Tests.ps1 index a6d424eae..92c4bdae3 100644 --- a/tests/unit/Cryptography.Tests.ps1 +++ b/tests/unit/Cryptography.Tests.ps1 @@ -4,8 +4,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} } Describe 'Invoke-PodeHMACSHA256Hash' { diff --git a/tests/unit/Endware.Tests.ps1 b/tests/unit/Endware.Tests.ps1 index d9560f4ac..e556563e3 100644 --- a/tests/unit/Endware.Tests.ps1 +++ b/tests/unit/Endware.Tests.ps1 @@ -6,8 +6,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} } Describe 'Invoke-PodeEndware' { diff --git a/tests/unit/Flash.Tests.ps1 b/tests/unit/Flash.Tests.ps1 index 92b53f730..2f59fc3d1 100644 --- a/tests/unit/Flash.Tests.ps1 +++ b/tests/unit/Flash.Tests.ps1 @@ -7,8 +7,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} } Describe 'Add-PodeFlashMessage' { diff --git a/tests/unit/Handlers.Tests.ps1 b/tests/unit/Handlers.Tests.ps1 index 6432787ed..ccb9d1256 100644 --- a/tests/unit/Handlers.Tests.ps1 +++ b/tests/unit/Handlers.Tests.ps1 @@ -8,8 +8,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} $PodeContext = @{ 'Server' = $null; } } diff --git a/tests/unit/Headers.Tests.ps1 b/tests/unit/Headers.Tests.ps1 index 6c4a923c6..c3fe90afe 100644 --- a/tests/unit/Headers.Tests.ps1 +++ b/tests/unit/Headers.Tests.ps1 @@ -6,8 +6,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} } Describe 'Test-PodeHeader' { Context 'WebServer' { diff --git a/tests/unit/Helpers.Tests.ps1 b/tests/unit/Helpers.Tests.ps1 index 23a806c31..67a1c842f 100644 --- a/tests/unit/Helpers.Tests.ps1 +++ b/tests/unit/Helpers.Tests.ps1 @@ -8,8 +8,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} } Describe 'Get-PodeType' { diff --git a/tests/unit/Mappers.Tests.ps1 b/tests/unit/Mappers.Tests.ps1 index 750b9bd9b..71054e904 100644 --- a/tests/unit/Mappers.Tests.ps1 +++ b/tests/unit/Mappers.Tests.ps1 @@ -6,8 +6,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} } Describe 'Get-PodeContentType' { Context 'No extension supplied' { diff --git a/tests/unit/Metrics.Tests.ps1 b/tests/unit/Metrics.Tests.ps1 index 698cae935..3e799aab8 100644 --- a/tests/unit/Metrics.Tests.ps1 +++ b/tests/unit/Metrics.Tests.ps1 @@ -6,8 +6,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} $PodeContext = @{ Metrics = @{ diff --git a/tests/unit/Middleware.Tests.ps1 b/tests/unit/Middleware.Tests.ps1 index abf2a36d9..e3af190d6 100644 --- a/tests/unit/Middleware.Tests.ps1 +++ b/tests/unit/Middleware.Tests.ps1 @@ -8,8 +8,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} } Describe 'Get-PodeInbuiltMiddleware' { diff --git a/tests/unit/NameGenerator.Tests.ps1 b/tests/unit/NameGenerator.Tests.ps1 index 257c63632..4844b2a06 100644 --- a/tests/unit/NameGenerator.Tests.ps1 +++ b/tests/unit/NameGenerator.Tests.ps1 @@ -4,8 +4,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} } Describe 'Get-PodeRandomName' { diff --git a/tests/unit/OpenApi.Tests.ps1 b/tests/unit/OpenApi.Tests.ps1 index f0b4ed932..69bf6d7f2 100644 --- a/tests/unit/OpenApi.Tests.ps1 +++ b/tests/unit/OpenApi.Tests.ps1 @@ -7,8 +7,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} } Describe 'OpenApi' { diff --git a/tests/unit/PrivateOpenApi.Tests.ps1 b/tests/unit/PrivateOpenApi.Tests.ps1 index b5bb665b4..ce56a1f7e 100644 --- a/tests/unit/PrivateOpenApi.Tests.ps1 +++ b/tests/unit/PrivateOpenApi.Tests.ps1 @@ -7,8 +7,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} } Describe 'PrivateOpenApi' { diff --git a/tests/unit/Responses.Tests.ps1 b/tests/unit/Responses.Tests.ps1 index 08f701e6d..f02e165e5 100644 --- a/tests/unit/Responses.Tests.ps1 +++ b/tests/unit/Responses.Tests.ps1 @@ -7,8 +7,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} } diff --git a/tests/unit/Routes.Tests.ps1 b/tests/unit/Routes.Tests.ps1 index 4261d9fbd..4a1c2e4bb 100644 --- a/tests/unit/Routes.Tests.ps1 +++ b/tests/unit/Routes.Tests.ps1 @@ -8,8 +8,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} $PodeContext = @{ 'Server' = $null; } } diff --git a/tests/unit/Schedules.Tests.ps1 b/tests/unit/Schedules.Tests.ps1 index 1ad2440a6..1488574c7 100644 --- a/tests/unit/Schedules.Tests.ps1 +++ b/tests/unit/Schedules.Tests.ps1 @@ -8,8 +8,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} } Describe 'Find-PodeSchedule' { Context 'Invalid parameters supplied' { @@ -132,7 +130,7 @@ Describe 'Add-PodeSchedule' { $start = ([DateTime]::Now.AddHours(3)) $end = ([DateTime]::Now.AddHours(5)) - Add-PodeSchedule -Name 'test' -Cron @('@minutely', '@hourly') -ScriptBlock { Write-Host 'hello' } -StartTime $start -EndTime $end + Add-PodeSchedule -Name 'test' -Cron @('@minutely', '@hourly') -ScriptBlock { Write-Host 'hello' } -StartTime $start -EndTime $end $schedule = $PodeContext.Schedules.Items['test'] $schedule | Should -Not -Be $null diff --git a/tests/unit/Security.Tests.ps1 b/tests/unit/Security.Tests.ps1 index 606377575..c1311c401 100644 --- a/tests/unit/Security.Tests.ps1 +++ b/tests/unit/Security.Tests.ps1 @@ -8,9 +8,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} - $PodeContext = @{ 'Server' = $null; } } Describe 'Test-PodeIPAccess' { diff --git a/tests/unit/Server.Tests.ps1 b/tests/unit/Server.Tests.ps1 index fcabafd29..12d0376fc 100644 --- a/tests/unit/Server.Tests.ps1 +++ b/tests/unit/Server.Tests.ps1 @@ -13,8 +13,6 @@ BeforeAll { RunspacePools = @{} } - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} } Describe 'Start-PodeInternalServer' { diff --git a/tests/unit/Serverless.Tests.ps1 b/tests/unit/Serverless.Tests.ps1 index 2a31e9f7f..5092d10d4 100644 --- a/tests/unit/Serverless.Tests.ps1 +++ b/tests/unit/Serverless.Tests.ps1 @@ -7,8 +7,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} Mock Test-PodeLoggerEnabled { return $false } } Describe 'Start-PodeAzFuncServer' { diff --git a/tests/unit/Sessions.Tests.ps1 b/tests/unit/Sessions.Tests.ps1 index c27bece15..239803ce1 100644 --- a/tests/unit/Sessions.Tests.ps1 +++ b/tests/unit/Sessions.Tests.ps1 @@ -7,8 +7,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} $now = [datetime]::UtcNow } diff --git a/tests/unit/State.Tests.ps1 b/tests/unit/State.Tests.ps1 index 6ef5f3f3f..56580e5e8 100644 --- a/tests/unit/State.Tests.ps1 +++ b/tests/unit/State.Tests.ps1 @@ -7,8 +7,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} $PodeContext = @{ 'Server' = $null; } } diff --git a/tests/unit/Timers.Tests.ps1 b/tests/unit/Timers.Tests.ps1 index 7756d4008..d25f7e4d0 100644 --- a/tests/unit/Timers.Tests.ps1 +++ b/tests/unit/Timers.Tests.ps1 @@ -8,8 +8,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - # Mock Write-PodeTraceLog to avoid load Pode C# component - Mock Write-PodeTraceLog {} $PodeContext = @{ 'Server' = $null; } } @@ -74,7 +72,7 @@ Describe 'Add-PodeTimer' { It 'Adds new timer to session with no limit' { $PodeContext = @{ 'Timers' = @{ Items = @{} }; } - Add-PodeTimer -Name 'test' -Interval 1 -ScriptBlock { Write-Host 'hello' } -Limit 0 -Skip 1 + Add-PodeTimer -Name 'test' -Interval 1 -ScriptBlock { Write-Host 'hello' } -Limit 0 -Skip 1 $timer = $PodeContext.Timers.Items['test'] $timer | Should -Not -Be $null From 4859585052e15039e2659be89aed5787be37cc69 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Fri, 25 Oct 2024 17:59:07 -0700 Subject: [PATCH 18/53] Fix Levels and cleanup --- docs/Tutorials/Logging/Types/Trace.md | 63 ------------ examples/Logging.ps1 | 3 +- src/Pode.psd1 | 2 - src/Private/Context.ps1 | 1 - src/Private/Logging.ps1 | 139 +------------------------- src/Private/Server.ps1 | 4 +- src/Public/Logging.ps1 | 119 ++++------------------ tests/unit/Server.Tests.ps1 | 1 + 8 files changed, 28 insertions(+), 304 deletions(-) delete mode 100644 docs/Tutorials/Logging/Types/Trace.md diff --git a/docs/Tutorials/Logging/Types/Trace.md b/docs/Tutorials/Logging/Types/Trace.md deleted file mode 100644 index 22297053f..000000000 --- a/docs/Tutorials/Logging/Types/Trace.md +++ /dev/null @@ -1,63 +0,0 @@ - -# Trace Logging - -Pode supports logging for any public function invocation and some important non-error messages using the inbuilt Trace logging method. This method enables comprehensive logging for various operations within Pode ensuring that important actions and messages are captured. - -## Enabling - -To enable and use the Trace logging you use the `Enable-PodeTraceLogging` function supplying a logging method from `New-PodeLoggingMethod`. - -## Examples - -### Log to Syslog - -The following example enables Trace logging and will output all items to a Syslog server: - -```powershell -$method = New-PodeLoggingMethod -Syslog -Server '127.0.0.1' -Transport 'UDP' -$method | Enable-PodeTraceLogging -``` - -### Using Raw Data - -The following example uses a Custom logging method and sets Trace logging to include raw log data in the logging output. The Custom method logs the raw data to the terminal: - -```powershell -$method = New-PodeLoggingMethod -Custom -ScriptBlock { - param($item) - "$($item | ConvertTo-Json -Depth 10)" | Out-Default -} - -$method | Enable-PodeTraceLogging -Raw -``` - -## Raw Trace Log Data - -The raw log data hashtable that will be supplied to any Custom logging methods will look as follows: - -```powershell -@{ - 'Server' = 'pode-dev' - 'Message' = 'Operation Add-PodeRoute invoked with parameters= ScriptBlock= Method=Get Path=/' - 'ThreadId' = 21 - 'Operation' = 'Add-PodeRoute' - 'Level' = 'Info' - 'Date' = '2024-06-18T21=31=36.4636345Z' - 'Parameters' = @{ - 'ScriptBlock' = @{ - 'Attributes' = '' - 'File' = 'C=\\Users\\pode\\Documents\\GitHub\\Pode\\examples\\logging.ps1' - 'IsFilter' = $false - 'IsConfiguration' = $false - 'Module' = $null - 'StartPosition' = 'System.Management.Automation.PSToken' - 'DebuggerHidden' = $false - 'Id' = '0de89f0a-4df2-4160-a605-97fd90c2ae2e' - 'Ast' = "{\r\n Write-PodeLog -Name 'mylog' -Message 'Something' -Level 'Info'\r\n }" - } - 'Method' = @('Get') - 'Path' = '/' - } - 'Tag' = 'Main' -} -``` \ No newline at end of file diff --git a/examples/Logging.ps1 b/examples/Logging.ps1 index ab6e91ace..87147a0c9 100644 --- a/examples/Logging.ps1 +++ b/examples/Logging.ps1 @@ -93,8 +93,7 @@ Start-PodeServer -browse { if ( $requestLogging) { $requestLogging | Enable-PodeRequestLogging } - - $logging | Enable-PodeTraceLogging -Raw:$Raw + $logging | Enable-PodeErrorLogging -Raw:$Raw -Levels * $logging | Enable-PodeCommonLogging -Name 'mylog' -Raw:$Raw diff --git a/src/Pode.psd1 b/src/Pode.psd1 index a60e26916..a65a21901 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -292,11 +292,9 @@ 'Enable-PodeRequestLogging', 'Enable-PodeErrorLogging', 'Enable-PodeCommonLogging', - 'Enable-PodeTraceLogging', 'Disable-PodeRequestLogging', 'Disable-PodeErrorLogging', 'Disable-PodeCommonLogging', - 'Disable-PodeTraceLogging', 'Add-PodeLogger', 'Remove-PodeLogger', 'Clear-PodeLogger', diff --git a/src/Private/Context.ps1 b/src/Private/Context.ps1 index 5d237e996..975e1aac6 100644 --- a/src/Private/Context.ps1 +++ b/src/Private/Context.ps1 @@ -582,7 +582,6 @@ function New-PodeRunspacePool { # setup main runspace pool $threadsCounts = @{ Default = 3 - Timer = 1 Log = 1 Schedule = 1 Misc = 1 diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index b84f6f040..272aee848 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -817,25 +817,6 @@ function Get-PodeErrorLoggingName { return '__pode_log_errors__' } -<# -.SYNOPSIS - Gets the name of the main logger. - -.DESCRIPTION - This function returns the name of the main logger used in Pode. - -.OUTPUTS - [string] - The name of the main logger. - - .EXAMPLE - Get-PodeTraceLoggingName -#> -function Get-PodeTraceLoggingName { - # Return the name of the main logger - return '__pode_log_trace__' -} - - <# .SYNOPSIS Retrieves a Pode logger by name. @@ -1171,7 +1152,7 @@ function Start-PodeLoggerDispatcher { Write-PodeErrorLog -Message $log.Item.Message -ThreadId $log.Item.ThreadId -Tag 'Listener' } else { - Write-PodeLog -Name (Get-PodeTraceLoggingName) -Message $log.Item.Message -Level $log.Item.Level -ThreadId $log.Item.ThreadId -Tag 'Listener' + Write-PodeErrorLog -Message $log.Item.Message -Level $log.Item.Level -ThreadId $log.Item.ThreadId -Tag 'Listener' } } continue @@ -1297,123 +1278,7 @@ function Test-PodeLoggerBatch { } } } - -<# -.SYNOPSIS - Writes an entry to the Pode trace log. - -.DESCRIPTION - Writes an entry to the Pode trace log. The entry can be based on either an operation and its parameters or a custom message. - This function handles the formatting of log entries and enqueues them for processing. - -.PARAMETER Operation - The operation being logged. - -.PARAMETER Parameters - A hashtable of parameters associated with the operation. - -.PARAMETER Message - A custom message to log. - -.EXAMPLE - Write-PodeTraceLog -Operation 'Remove-PodeLogger' -Parameters @{ Name = 'LogName' } - -.EXAMPLE - Write-PodeTraceLog -Message 'Custom log message.' - -.NOTES - This is an internal function and may change in future releases of Pode. -#> -function Write-PodeTraceLog { - [CmdletBinding(DefaultParameterSetName = 'Parameter')] - param( - [Parameter(Mandatory, ParameterSetName = 'Parameter')] - [string] - $Operation, - - [Parameter(Mandatory, ParameterSetName = 'Parameter')] - [hashtable] - $Parameters, - - [Parameter(Mandatory, ParameterSetName = 'Message')] - [string] - $Message - ) - - # Do nothing if logging is disabled, or error logging isn't set up - $name = Get-PodeTraceLoggingName - if (!(Test-PodeLoggerEnabled -Name $name)) { - return - } - - # Determine the parameter set and build the log message - switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { - 'parameter' { - $Message = if ($Parameters) { - $paramString = ($Parameters.GetEnumerator() | ForEach-Object { - if ($_.Value -is [scriptblock]) { - "$($_.Key)=" - } - elseif ($_.Key -eq 'ArgumentList') { - "$($_.Key)=" - } - elseif ($_.Key -eq 'Route') { - "$($_.Key)={ Path : `"$($_.Value.Path -join ',')`" ,Method : `"$($_.Value.Method -join ',')`" }" - } - elseif ($_.Key -eq 'ExternalDoc') { - "$($_.Key)=$($_.Value|ConvertTo-Json -Compress)" - } - elseif ($_.Key -eq 'Scheme') { - "$($_.Key)={ Name : `"$($_.Value.Name)`" , Scheme : `"$($_.Value.Scheme)`" }" - } - elseif ($_.Key -eq 'InputObject') { - "$($_.Key)=" - } - else { - "$($_.Key)=$($_.Value)" - } - }) -join ', ' - "Operation $Operation invoked with parameters: $paramString" - } - else { - "Operation $Operation invoked with no parameters" - } - break - } - 'Message' { - $Operation = '-' - $Parameters = @{} - break - } - } - - # Determine the current date and time, respecting the AsUTC setting - if ($PodeContext.Server.Logging.Type[$Name].Method.Arguments.AsUTC) { - $date = [datetime]::UtcNow - } - else { - $date = [datetime]::Now - } - - # Build the log item - $item = @{ - Parameters = $Parameters - Message = $Message - Operation = $Operation - Level = 'Info' - Server = $PodeContext.Server.ComputerName - Tag = 'Main' - Date = $date - ThreadId = [System.Threading.Thread]::CurrentThread.ManagedThreadId - } - - # Add the log item to the processing queue - $null = [Pode.PodeLogger]::Enqueue(@{ - Name = $name - Item = $item - }) -} - + <# .SYNOPSIS Creates a new log batch information object. diff --git a/src/Private/Server.ps1 b/src/Private/Server.ps1 index 6b4477fca..59bc76cce 100644 --- a/src/Private/Server.ps1 +++ b/src/Private/Server.ps1 @@ -147,7 +147,7 @@ function Start-PodeInternalServer { # run running event hooks Invoke-PodeEvent -Type Running - Write-PodeTraceLog -Message "Pode $(Get-PodeVersion) (PID: $($PID))" + Write-PodeErrorLog -Message "Pode $(Get-PodeVersion) (PID: $($PID))" -Level Info # state what endpoints are being listened on if ($endpoints.Length -gt 0) { @@ -173,7 +173,7 @@ function Start-PodeInternalServer { $urlAndFlags += "$($_.Url) $($flags)" } - Write-PodeTraceLog -Message "$msg - $($urlAndFlags -join ' , ')" + Write-PodeErrorLog -Message "$msg - $($urlAndFlags -join ' , ')" -Level Info # state the OpenAPI endpoints for each definition foreach ($key in $PodeContext.Server.OpenAPI.Definitions.keys) { diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 55239f68a..822aeeb88 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -1146,7 +1146,7 @@ function Enable-PodeErrorLogging { [Parameter()] [ValidateNotNullOrEmpty()] - [ValidateSet('Error', 'Warning', 'Informational', 'Verbose', 'Debug', '*')] + [ValidateSet('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Info', 'Verbose', 'Debug', '*')] [string[]] $Levels = @('Error'), @@ -1230,8 +1230,11 @@ function Enable-PodeCommonLogging { [hashtable[]] $Method, + [Parameter()] + [ValidateNotNullOrEmpty()] + [ValidateSet('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Info', 'Verbose', 'Debug', '*')] [string[]] - $Levels = @('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Info', 'Verbose', 'Debug'), + $Levels = @('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Info'), [Parameter(Mandatory = $true)] [string] @@ -1247,6 +1250,10 @@ function Enable-PodeCommonLogging { throw ($PodeLocale.loggingAlreadyEnabledExceptionMessage -f $Name) } + if ($Levels -contains '*') { + $Levels = @('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Info', 'Verbose', 'Debug') + } + } process { @@ -1279,73 +1286,6 @@ function Enable-PodeCommonLogging { } } - - -<# -.SYNOPSIS - Enables the trace logging in Pode. - -.DESCRIPTION - This function enables the trace logging in Pode, allowing logs to be written based on the defined method and log levels. It ensures the method is not already enabled and validates the provided script block. - -.PARAMETER Method - The hashtable defining the logging method, including the ScriptBlock for log output. - -.PARAMETER Raw - If set, the raw log data will be included in the logging output. - -.EXAMPLE - $method = New-PodeLoggingMethod -syslog -Server 127.0.0.1 -Transport UDP - $method | Enable-PodeTraceLogging -#> -function Enable-PodeTraceLogging { - [CmdletBinding()] - param( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [hashtable[]] - $Method, - - [switch] - $Raw - ) - begin { - $pipelineMethods = @() - $name = Get-PodeTraceLoggingName - # error if it's already enabled - if ($PodeContext.Server.Logging.Type.Contains($name)) { - throw ($PodeLocale.loggingAlreadyEnabledExceptionMessage -f $name) - } - } - - process { - # ensure the Method contains a scriptblock - if ((! $PodeContext.Server.Logging.Method.ContainsKey($_.Id)) -and (! $_.ContainsKey('Scriptblock'))) { - # The supplied output Method for the '{0}' Logging method requires a valid ScriptBlock. - throw ($PodeLocale.loggingMethodRequiresValidScriptBlockExceptionMessage -f 'Main') - } - $pipelineMethods += $_ - } - - end { - - if ($pipelineMethods.Count -gt 1) { - $Method = $pipelineMethods - } - - # add the error logger - $PodeContext.Server.Logging.Type[$name] = @{ - Method = $Method - ScriptBlock = (Get-PodeLoggingInbuiltType -Type Main) - Arguments = @{ - Raw = $Raw - DataFormat = $Method.Arguments.DataFormat - } - Standard = $true - } - $Method.ForEach({ $_.Logger += $name }) - } -} - <# .SYNOPSIS Disables Request Logging. @@ -1387,24 +1327,6 @@ function Disable-PodeCommonLogging { Remove-PodeLogger -Name $Name } - -<# -.SYNOPSIS -Disables the trace logging method in Pode. - -.DESCRIPTION -This function disables the trace logging method in Pode. - -.EXAMPLE -Disable-PodeTraceLogging -#> -function Disable-PodeTraceLogging { - [CmdletBinding()] - param() - - Remove-PodeLogger -Name (Get-PodeTraceLoggingName) -} - <# .SYNOPSIS Disables Error Logging. @@ -1486,9 +1408,6 @@ function Add-PodeLogger { throw ($PodeLocale.fnDoesNotAcceptArrayAsPipelineInputExceptionMessage -f $($MyInvocation.MyCommand.Name)) } - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - # ensure the name doesn't already exist if ($PodeContext.Server.Logging.Type.ContainsKey($Name)) { # Logging method already defined @@ -1536,8 +1455,6 @@ function Remove-PodeLogger { $Name ) Process { - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters # Check if the specified logging type exists if ($PodeContext.Server.Logging.Type.Contains($Name)) { @@ -1591,9 +1508,6 @@ function Clear-PodeLogger { [CmdletBinding()] param() - # Record the operation on the trace log - Write-PodeTraceLog -Operation $MyInvocation.MyCommand.Name -Parameters $PSBoundParameters - $PodeContext.Server.Logging.Type.Clear() } @@ -1630,6 +1544,10 @@ if (!(Test-Path Alias:Clear-PodeLoggers)) { .PARAMETER ThreadId The ID of the thread where the error occurred. If not specified, the current thread's ID is used. +.PARAMETER Tag + A string that identifies the source application, service, or process generating the log message. + The tag helps distinguish log messages from different sources, making it easier to filter and analyze logs. Default is '-'. + .EXAMPLE try { # Some operation @@ -1660,14 +1578,17 @@ function Write-PodeErrorLog { [Parameter()] [ValidateNotNullOrEmpty()] - [ValidateSet('Error', 'Warning', 'Informational', 'Verbose', 'Debug')] + [ValidateSet('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Info', 'Verbose', 'Debug' )] [string] $Level = 'Error', [Parameter(ParameterSetName = 'Exception')] [switch] $CheckInnerException, [Parameter()] - [int] $ThreadId + [int] $ThreadId, + + [string] + $Tag = '-' ) Process { @@ -1685,6 +1606,7 @@ function Write-PodeErrorLog { Category = $Exception.Source Message = $Exception.Message StackTrace = $Exception.StackTrace + Tag = $Tag } } 'errorrecord' { @@ -1692,12 +1614,14 @@ function Write-PodeErrorLog { Category = $ErrorRecord.CategoryInfo.ToString() Message = $ErrorRecord.Exception.Message StackTrace = $ErrorRecord.ScriptStackTrace + Tag = $Tag } } 'message' { $item = @{ Category = $Category.ToString() Message = $Message + Tag = $Tag } } } @@ -1806,6 +1730,7 @@ function Write-PodeLog { [Parameter( ParameterSetName = 'InputObject')] [Parameter( ParameterSetName = 'custom')] + [ValidateSet('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Info', 'Verbose', 'Debug')] [string] $Level = 'Informational', diff --git a/tests/unit/Server.Tests.ps1 b/tests/unit/Server.Tests.ps1 index 12d0376fc..d10ac4640 100644 --- a/tests/unit/Server.Tests.ps1 +++ b/tests/unit/Server.Tests.ps1 @@ -39,6 +39,7 @@ Describe 'Start-PodeInternalServer' { Mock Write-Verbose { } Mock Add-PodeScopedVariablesInbuilt { } Mock Write-PodeHost { } + Mock Write-PodeErrorLog { } } It 'Calls one-off script logic' { From 1be91635b7fcbaac4386024708e7aa57044a7edb Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sun, 27 Oct 2024 20:04:52 -0700 Subject: [PATCH 19/53] functions change --- docs/Tutorials/Logging/Types/General.md | 10 +- examples/Logging.ps1 | 7 +- src/Pode.psd1 | 7 +- src/Private/Logging.ps1 | 90 ++++++++- src/Public/Logging.ps1 | 254 ++++++++++++++++++++---- 5 files changed, 315 insertions(+), 53 deletions(-) diff --git a/docs/Tutorials/Logging/Types/General.md b/docs/Tutorials/Logging/Types/General.md index 2db0505fa..3ddac7233 100644 --- a/docs/Tutorials/Logging/Types/General.md +++ b/docs/Tutorials/Logging/Types/General.md @@ -3,11 +3,11 @@ Pode supports general logging, allowing you to define custom logging methods and log levels. This feature enables you to write logs based on specified methods, ensuring flexibility and control over logging outputs. -To enable general logging, use the `Enable-PodeCommonLogging` function. This function takes a hashtable defining the logging method, including a ScriptBlock for log output. You can specify various log levels to be enabled, such as Error, Emergency, Alert, Critical, Warning, Notice, Informational, Info, Verbose, and Debug. +To enable general logging, use the `Add-PodeLogging` function. This function takes a hashtable defining the logging method, including a ScriptBlock for log output. You can specify various log levels to be enabled, such as Error, Emergency, Alert, Critical, Warning, Notice, Informational, Info, Verbose, and Debug. ## Enabling General Logging -To enable general logging, use the `Enable-PodeCommonLogging` function, supplying the necessary parameters: +To enable general logging, use the `Add-PodeLogging` function, supplying the necessary parameters: - `Method`: The hashtable defining the logging method, including the ScriptBlock for log output. - `Levels`: An array of log levels to be enabled for the logging method (default includes Error, Emergency, Alert, Critical, Warning, Notice, Informational, Info, Verbose, Debug). @@ -18,17 +18,17 @@ To enable general logging, use the `Enable-PodeCommonLogging` function, supplyin ```powershell $method = New-PodeLoggingMethod -syslog -Server 127.0.0.1 -Transport UDP -$method | Enable-PodeCommonLogging -Name "mysyslog" +$method | Add-PodeLogging -Name "mysyslog" ``` ## Disabling General Logging -To disable a general logging method, use the `Disable-PodeCommonLogging` function with the `Name` parameter: +To disable a general logging method, use the `Remove-PodeLogging` function with the `Name` parameter: ### Example ```powershell -Disable-PodeCommonLogging -Name 'mysyslog' +Remove-PodeLogging -Name 'mysyslog' ``` With these functions, Pode ensures robust and customizable logging capabilities, allowing you to manage logs effectively based on your specific requirements. diff --git a/examples/Logging.ps1 b/examples/Logging.ps1 index 87147a0c9..0bdbad2ca 100644 --- a/examples/Logging.ps1 +++ b/examples/Logging.ps1 @@ -93,9 +93,10 @@ Start-PodeServer -browse { if ( $requestLogging) { $requestLogging | Enable-PodeRequestLogging } - - $logging | Enable-PodeErrorLogging -Raw:$Raw -Levels * - $logging | Enable-PodeCommonLogging -Name 'mylog' -Raw:$Raw + + $logging | Enable-PodeErrorLogging -Raw:$Raw -Levels Error + $logging | Enable-PodeMainLogging -Raw:$Raw + $logging | Add-PodeLogging -Name 'mylog' -Raw:$Raw Write-PodeLog -Name 'mylog' -Message 'just started' -Level 'Info' # GET request for web page on "localhost:8081/" diff --git a/src/Pode.psd1 b/src/Pode.psd1 index a65a21901..a8e6cc11e 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -291,10 +291,12 @@ 'New-PodeTerminalLoggingMethod' 'Enable-PodeRequestLogging', 'Enable-PodeErrorLogging', - 'Enable-PodeCommonLogging', + 'Enable-PodeMainLogging', + 'Add-PodeLogging', 'Disable-PodeRequestLogging', 'Disable-PodeErrorLogging', - 'Disable-PodeCommonLogging', + 'Disable-PodeMainLogging', + 'Remove-PodeLogging', 'Add-PodeLogger', 'Remove-PodeLogger', 'Clear-PodeLogger', @@ -305,6 +307,7 @@ 'Enable-PodeLogging', 'Disable-PodeLogging', 'Clear-PodeLogging', + 'Get-PodeLoggingLevel', # core 'Start-PodeServer', diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index 1f428f246..9736b39d7 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -150,9 +150,9 @@ function Get-PodeLoggingFileMethod { if (($Options.MaxDays -gt 0) -and ($Options.NextClearDown -lt [DateTime]::Now.Date)) { $date = [DateTime]::Now.Date.AddDays(-$Options.MaxDays) - $null = Get-ChildItem -Path $options.Path -Filter '*.log' -Force | - Where-Object { $_.CreationTime -lt $date } | - Remove-Item -Force + $null = Get-ChildItem -Path $options.Path -Filter '*.log' -Force | + Where-Object { $_.CreationTime -lt $date } | + Remove-Item -Force $Options.NextClearDown = [DateTime]::Now.Date.AddDays(1) } @@ -730,8 +730,7 @@ function Get-PodeLoggingInbuiltType { if ($options.Raw) { return $item } - - + # Optimized concatenation using Append return [System.Text.StringBuilder]::new(). Append('Date: ').Append($item.Date.ToString($options.DataFormat)).Append('Level: ').Append($item.Level). Append('ThreadId: ').Append($item.ThreadId).Append('Server: ').Append($item.Server).Append('Category: '). @@ -817,6 +816,24 @@ function Get-PodeErrorLoggingName { return '__pode_log_errors__' } + +<# +.SYNOPSIS +Gets the name of the Main logger. + +.DESCRIPTION +This function returns the name of the logger used for logging Mains in Pode. + +.RETURNS +[string] - The name of the Main logger. + +.EXAMPLE +Get-PodeMainLoggingName +#> +function Get-PodeMainLoggingName { + # Return the name of the Main logger + return '__pode_log_Mains__' +} <# .SYNOPSIS Retrieves a Pode logger by name. @@ -1144,8 +1161,7 @@ function Start-PodeLoggerDispatcher { if ($log.Name -eq 'Listener') { if ($log.Item -is [System.Exception]) { - - Write-PodeErrorLog -Exception $log.Item -Level = 'Error' -ThreadId $log.Item.ThreadId + Write-PodeErrorLog -Exception $log.Item -Level 'Error' -ThreadId $log.Item.ThreadId } else { if ($log.Item.Level -eq [Pode.PodeLoggingLevel]::Error) { @@ -1374,3 +1390,63 @@ function Test-PodeDateFormat { return $false } } + + + +function Enable-PodeLoggingInternal { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true )] + [hashtable[]] + $Method, + + [Parameter(Mandatory = $true)] + [ValidateSet('Errors', 'Main' )] + [string] + $Type, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [ValidateSet('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Verbose', 'Debug', '*')] + [string[]] + $Levels , + + [switch] + $Raw + ) + switch ($Type.ToLowerInvariant()) { + + 'errors' { + $name = Get-PodeErrorLoggingName + $scriptBlock = (Get-PodeLoggingInbuiltType -Type Errors) + } + 'main' { + $name = Get-PodeMainLoggingName + $scriptBlock = (Get-PodeLoggingInbuiltType -Type Main) + } + } + # error if it's already enabled + if ($PodeContext.Server.Logging.Type.Contains($Name)) { + # Error Logging has already been enabled + throw ($PodeLocale.loggingAlreadyEnabledExceptionMessage -f 'Error') + } + # all errors? + if ($Levels -contains '*') { + $Levels = @('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Verbose', 'Debug') + } + + # add the error logger + $PodeContext.Server.Logging.Type[$name] = @{ + Method = $Method + ScriptBlock = $scriptBlock + Arguments = @{ + Raw = $Raw + Levels = $Levels + DataFormat = $Method.Arguments.DataFormat + } + Standard = $true + } + + $Method.ForEach({ $_.Logger += $name }) + +} \ No newline at end of file diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 822aeeb88..6ac305834 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -1,5 +1,3 @@ - - <# .SYNOPSIS Creates a new terminal logging method in Pode. @@ -1146,7 +1144,7 @@ function Enable-PodeErrorLogging { [Parameter()] [ValidateNotNullOrEmpty()] - [ValidateSet('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Info', 'Verbose', 'Debug', '*')] + [ValidateSet('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Verbose', 'Debug', '*')] [string[]] $Levels = @('Error'), @@ -1157,7 +1155,7 @@ function Enable-PodeErrorLogging { begin { $pipelineMethods = @() - $name = Get-PodeErrorLoggingName + <# $name = Get-PodeErrorLoggingName # error if it's already enabled if ($PodeContext.Server.Logging.Type.Contains($Name)) { # Error Logging has already been enabled @@ -1165,8 +1163,8 @@ function Enable-PodeErrorLogging { } # all errors? if ($Levels -contains '*') { - $Levels = @('Error', 'Warning', 'Informational', 'Verbose', 'Debug') - } + $Levels = @('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Verbose', 'Debug') + }#> } process { @@ -1184,7 +1182,7 @@ function Enable-PodeErrorLogging { $Method = $pipelineMethods } - # add the error logger + <# # add the error logger $PodeContext.Server.Logging.Type[$name] = @{ Method = $Method ScriptBlock = (Get-PodeLoggingInbuiltType -Type Errors) @@ -1195,11 +1193,70 @@ function Enable-PodeErrorLogging { } Standard = $true } - +#> + Enable-PodeLoggingInternal -Method $Method -Type Errors -Levels $Levels -Raw:$Raw $Method.ForEach({ $_.Logger += $name }) } } +<# +.SYNOPSIS + Enables Main Logging using a supplied output method. + +.DESCRIPTION + Enables Main Logging using a supplied output method. + +.PARAMETER Method + The Method to use for output the log entry (From New-PodeLoggingMethod). + +.PARAMETER Levels + The Levels of Mains that should be logged (default is 'Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational'). + +.PARAMETER Raw + If supplied, the log item returned will be the raw Main item as a hashtable and not a string (for Custom methods). + +.EXAMPLE + New-PodeLoggingMethod -Terminal | Enable-PodeMainLogging +#> +function Enable-PodeMainLogging { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [hashtable[]] + $Method, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [ValidateSet('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Verbose', 'Debug', '*')] + [string[]] + $Levels = @('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational'), + + [switch] + $Raw + ) + + begin { + $pipelineMethods = @() + } + + process { + # ensure the Method contains a scriptblock + if ((! $PodeContext.Server.Logging.Method.ContainsKey($_.Id)) -and (! $_.ContainsKey('Scriptblock'))) { + # The supplied output Method for Error Logging requires a valid ScriptBlock + throw ($PodeLocale.loggingMethodRequiresValidScriptBlockExceptionMessage -f 'Error') + } + $pipelineMethods += $_ + } + + end { + + if ($pipelineMethods.Count -gt 1) { + $Method = $pipelineMethods + } + Enable-PodeLoggingInternal -Method $Method -Type Main -Levels $Levels -Raw:$Raw + $Method.ForEach({ $_.Logger += $name }) + } +} <# .SYNOPSIS Enables a generic logging method in Pode. @@ -1221,25 +1278,25 @@ function Enable-PodeErrorLogging { .EXAMPLE $method = New-PodeLoggingMethod -syslog -Server 127.0.0.1 -Transport UDP - $method | Enable-PodeCommonLogging -Name "mysyslog" + $method | Add-PodeLogging -Name "mysyslog" #> -function Enable-PodeCommonLogging { +function Add-PodeLogging { [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [hashtable[]] $Method, - [Parameter()] - [ValidateNotNullOrEmpty()] - [ValidateSet('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Info', 'Verbose', 'Debug', '*')] - [string[]] - $Levels = @('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Info'), - [Parameter(Mandatory = $true)] [string] $Name, + [Parameter()] + [ValidateNotNullOrEmpty()] + [ValidateSet('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Verbose', 'Debug', '*')] + [string[]] + $Levels = @('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational'), + [switch] $Raw ) @@ -1314,9 +1371,9 @@ function Disable-PodeRequestLogging { The name of the logging method to be disable. .EXAMPLE - Disable-PodeCommonLogging -Name 'TestLog' + Remove-PodeLogging -Name 'TestLog' #> -function Disable-PodeCommonLogging { +function Remove-PodeLogging { [CmdletBinding()] param( [Parameter(Mandatory = $true)] @@ -1345,6 +1402,25 @@ function Disable-PodeErrorLogging { } + +<# +.SYNOPSIS +Disables Main Logging. + +.DESCRIPTION +Disables Main Logging. + +.EXAMPLE +Disable-PodeMainLogging +#> +function Disable-PodeMainLogging { + [CmdletBinding()] + param() + + Remove-PodeLogger -Name (Get-PodeMainLoggingName) + +} + <# .SYNOPSIS Adds a custom Logging method for parsing custom log items. @@ -1561,6 +1637,7 @@ if (!(Test-Path Alias:Clear-PodeLoggers)) { .EXAMPLE Write-PodeErrorLog -Message "Custom message" -Category NotSpecified -Level 'Warning' #> + function Write-PodeErrorLog { [CmdletBinding()] param( @@ -1592,6 +1669,10 @@ function Write-PodeErrorLog { ) Process { + + Write-PodeLog @PSBoundParameters -name (Get-PodeErrorLoggingName) -SuppressErrorLog + + <# # Check if logging is enabled and the error level is valid $name = Get-PodeErrorLoggingName if (!(Test-PodeLoggerEnabled -Name $name)) { return } @@ -1654,7 +1735,10 @@ function Write-PodeErrorLog { if ($CheckInnerException -and ($null -ne $Exception.InnerException) -and ![string]::IsNullOrWhiteSpace($Exception.InnerException.Message)) { $Exception.InnerException | Write-PodeErrorLog -Level $Level -ThreadId $ThreadId } +#> } + + } @@ -1690,6 +1774,12 @@ function Write-PodeErrorLog { .PARAMETER Exception An exception object to log. Required for the 'Exception' parameter set. +.PARAMETER ErrorRecord + The error record to log. This is used when handling errors through PowerShell's error handling mechanism. + +.PARAMETER Category + The category of the custom error message (Default: NotSpecified). + .PARAMETER CheckInnerException If specified, any inner exceptions of the provided exception are also logged. @@ -1710,14 +1800,14 @@ function Write-PodeErrorLog { } #> function Write-PodeLog { - [CmdletBinding(DefaultParameterSetName = 'custom')] + [CmdletBinding(DefaultParameterSetName = 'Message')] param( [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'InputObject')] - [object] + [psobject] $InputObject, [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Exception')] @@ -1728,38 +1818,42 @@ function Write-PodeLog { [switch] $CheckInnerException, - [Parameter( ParameterSetName = 'InputObject')] - [Parameter( ParameterSetName = 'custom')] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ErrorRecord')] + [System.Management.Automation.ErrorRecord] $ErrorRecord, + + [Parameter()] + [ValidateNotNullOrEmpty()] [ValidateSet('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Info', 'Verbose', 'Debug')] [string] - $Level = 'Informational', + $Level, - [Parameter( Mandatory = $true, ParameterSetName = 'custom')] + [Parameter( Mandatory = $true, ParameterSetName = 'Message')] [string] $Message, - [Parameter( ParameterSetName = 'custom')] + [Parameter()] [string] $Tag = '-', + [Parameter(ParameterSetName = 'InputObject')] + [Parameter(ParameterSetName = 'Message')] + [System.Management.Automation.ErrorCategory] $Category = [System.Management.Automation.ErrorCategory]::NotSpecified, + [Parameter()] [int] $ThreadId, [Parameter()] + [switch] $SuppressErrorLog ) Process { - # Skip logging if the logger is disabled or not set up. - if (!(Test-PodeLoggerEnabled -Name $Name)) { - return - } - # Define the log item based on the selected parameter set. switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'inputobject' { if (!$Level) { $Level = 'Informational' } + if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } $logItem = @{ Name = $Name Item = $InputObject @@ -1767,20 +1861,23 @@ function Write-PodeLog { } break } - 'custom' { + 'message' { if (!$Level) { $Level = 'Informational' } + if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } $logItem = @{ Name = $Name Item = @{ - Level = $Level - Message = $Message - Tag = $Tag + Category = $Category.ToString() + Level = $Level + Message = $Message + Tag = $Tag } } break } 'exception' { if (!$Level) { $Level = 'Error' } + if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } $logItem = @{ Name = $Name Item = @{ @@ -1789,6 +1886,19 @@ function Write-PodeLog { Tag = $Tag } } + + } + 'errorrecord' { + if (!$Level) { $Level = 'Error' } + if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } + $logItem = @{ + Name = $Name + Item = @{ + Level = $Level + Message = $ErrorRecord.Exception.Message + Tag = $Tag + } + } } } @@ -1806,12 +1916,53 @@ function Write-PodeLog { $logItem.Item.ThreadId = if ($ThreadId) { $ThreadId } else { [System.Threading.Thread]::CurrentThread.ManagedThreadId } # If error logging is not suppressed, log errors or exceptions. - if (! $SuppressErrorLog.IsPresent) { + if ((! $SuppressErrorLog.IsPresent) -and (Test-PodeErrorLoggingEnabled)) { if ($PSCmdlet.ParameterSetName.ToLowerInvariant() -eq 'exception') { + + [Pode.PodeLogger]::Enqueue( @{ + Name = Get-PodeErrorLoggingName + Item = @{ + Server = $logItem.Item.Server + Level = $Level + Date = $(if ($PodeContext.Server.Logging.Type[$Name].Method.Arguments.AsUTC) { $logItem.Item.Date.ToUniversalTime() }else { $logItem.Item.Date.ToLocaltime() }) + Category = $Exception.Source + Message = $Exception.Message + StackTrace = $Exception.StackTrace + Tag = $Tag + ThreadId = $logItem.Item.ThreadId + } + }) + Write-PodeErrorLog -Exception $Exception -Level $Level -CheckInnerException:$CheckInnerException -ThreadId $ThreadId } + elseif ($PSCmdlet.ParameterSetName.ToLowerInvariant() -eq 'errorrecord') { + [Pode.PodeLogger]::Enqueue( @{ + Name = Get-PodeErrorLoggingName + Item = @{ + Server = $logItem.Item.Server + Level = $Level + Date = $(if ($PodeContext.Server.Logging.Type[$Name].Method.Arguments.AsUTC) { $logItem.Item.Date.ToUniversalTime() }else { $logItem.Item.Date.ToLocaltime() }) + Category = $ErrorRecord.CategoryInfo.ToString() + Message = $ErrorRecord.Exception.Message + StackTrace = $ErrorRecord.ScriptStackTrace + Tag = $Tag + ThreadId = $logItem.Item.ThreadId + } + }) + } elseif ($Level -eq 'Error') { - Write-PodeErrorLog -Message $Message -Level $Level -ThreadId $ThreadId + [Pode.PodeLogger]::Enqueue( @{ + Name = Get-PodeErrorLoggingName + Item = @{ + Server = $logItem.Item.Server + Level = $Level + Date = $(if ($PodeContext.Server.Logging.Type[$Name].Method.Arguments.AsUTC) { $logItem.Item.Date.ToUniversalTime() }else { $logItem.Item.Date.ToLocaltime() }) + Category = $Category.ToString() + Message = $Message + Tag = $Tag + ThreadId = $logItem.Item.ThreadId + } + }) } } } @@ -1988,4 +2139,35 @@ function Disable-PodeLogging { #> function Clear-PodeLogging { [pode.PodeLogger]::Clear() +} + +<# +.SYNOPSIS + Retrieves the logging levels for a specified Pode logger. + +.DESCRIPTION + The `Get-PodeLoggingLevel` function takes the name of a logger and returns its associated logging levels. This function verifies whether the logger exists before attempting to retrieve its levels. + +.PARAMETER Name + The name of the logger for which to retrieve the logging levels. This parameter is mandatory. + +.OUTPUTS + An array of strings representing the logging levels of the specified Pode logger. If the logger does not exist, an empty array is returned. + +.EXAMPLE + Get-PodeLoggingLevel -Name 'FileLogger' + + This command retrieves the logging levels for the logger named 'FileLogger'. +#> +function Get-PodeLoggingLevel { + param( + [Parameter(Mandatory = $true)] + [string] + $Name + ) + + if (Test-PodeStandardLogger -Name $Name) { + return (Get-PodeLogger -Name $Name).Arguments.Levels + } + return @() } \ No newline at end of file From fe20cfb92c888bd72ce0cd2775094a394725d49a Mon Sep 17 00:00:00 2001 From: mdaneri Date: Mon, 28 Oct 2024 07:35:57 -0700 Subject: [PATCH 20/53] Fix tests + bugs post merge --- src/Listener/PodeContext.cs | 14 +++++++------- src/Listener/PodeRequest.cs | 10 +++++----- src/Listener/PodeSocket.cs | 8 ++++---- src/Public/Logging.ps1 | 2 +- tests/unit/Logging.Tests.ps1 | 1 + 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/Listener/PodeContext.cs b/src/Listener/PodeContext.cs index b0e3380ee..14c22a654 100644 --- a/src/Listener/PodeContext.cs +++ b/src/Listener/PodeContext.cs @@ -130,15 +130,15 @@ private void TimeoutCallback(object state) { try { - PodeHelpers.WriteErrorMessage("TimeoutCallback triggered", Listener, PodeLoggingLevel.Debug, this); + PodeLogger.WriteErrorMessage("TimeoutCallback triggered", Listener, PodeLoggingLevel.Debug, this); if (Response.SseEnabled || Request.IsWebSocket) { - PodeHelpers.WriteErrorMessage("Timeout ignored due to SSE/WebSocket", Listener, PodeLoggingLevel.Debug, this); + PodeLogger.WriteErrorMessage("Timeout ignored due to SSE/WebSocket", Listener, PodeLoggingLevel.Debug, this); return; } - PodeHelpers.WriteErrorMessage($"Request timeout reached: {Listener.RequestTimeout} seconds", Listener, PodeLoggingLevel.Warning, this); + PodeLogger.WriteErrorMessage($"Request timeout reached: {Listener.RequestTimeout} seconds", Listener, PodeLoggingLevel.Warning, this); ContextTimeoutToken.Cancel(); State = PodeContextState.Timeout; @@ -148,11 +148,11 @@ private void TimeoutCallback(object state) Request.Error.Data.Add("PodeStatusCode", 408); Dispose(); - PodeHelpers.WriteErrorMessage($"Request timeout reached: Dispose", Listener, PodeLoggingLevel.Debug, this); + PodeLogger.WriteErrorMessage($"Request timeout reached: Dispose", Listener, PodeLoggingLevel.Debug, this); } catch (Exception ex) { - PodeHelpers.WriteErrorMessage($"Exception in TimeoutCallback: {ex}", Listener, PodeLoggingLevel.Error); + PodeLogger.WriteErrorMessage($"Exception in TimeoutCallback: {ex}", Listener, PodeLoggingLevel.Error); } } @@ -303,7 +303,7 @@ public async Task Receive() } catch (OperationCanceledException ex) when (ContextTimeoutToken.IsCancellationRequested) { - PodeHelpers.WriteErrorMessage("Request timed out during receive operation", Listener, PodeLoggingLevel.Warning, this); + PodeLogger.WriteErrorMessage("Request timed out during receive operation", Listener, PodeLoggingLevel.Warning, this); State = PodeContextState.Timeout; // Explicitly set the state to Timeout var timeoutException = new HttpRequestException("Request timed out", ex); timeoutException.Data.Add("PodeStatusCode", 408); @@ -485,7 +485,7 @@ public void Dispose(bool force) } catch (Exception ex) { - PodeHelpers.WriteException(ex, Listener, PodeLoggingLevel.Error); + PodeLogger.WriteException(ex, Listener, PodeLoggingLevel.Error); } finally { diff --git a/src/Listener/PodeRequest.cs b/src/Listener/PodeRequest.cs index c0b4917b1..a1391f827 100644 --- a/src/Listener/PodeRequest.cs +++ b/src/Listener/PodeRequest.cs @@ -146,9 +146,9 @@ public async Task UpgradeToSSL(CancellationToken cancellationToken) InputStream = ssl; SslUpgraded = true; } - catch (OperationCanceledException ex) { PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose); } - catch (IOException ex) { PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose); } - catch (ObjectDisposedException ex) { PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose); } + catch (OperationCanceledException ex) { PodeLogger.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose); } + catch (IOException ex) { PodeLogger.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose); } + catch (ObjectDisposedException ex) { PodeLogger.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose); } catch (Exception ex) { PodeLogger.WriteException(ex, Context.Listener, PodeLoggingLevel.Error); @@ -238,11 +238,11 @@ public async Task Receive(CancellationToken cancellationToken) } catch (OperationCanceledException ex) { - PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose); + PodeLogger.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose); } catch (IOException ex) { - PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose); + PodeLogger.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose); } catch (HttpRequestException httpex) { diff --git a/src/Listener/PodeSocket.cs b/src/Listener/PodeSocket.cs index 752451d07..37d49a4c6 100644 --- a/src/Listener/PodeSocket.cs +++ b/src/Listener/PodeSocket.cs @@ -207,8 +207,8 @@ public void StartReceive(PodeContext context) // Run the receive operation asynchronously in a new task. _ = Task.Run(async () => await context.Receive().ConfigureAwait(false), Listener.CancellationToken); } - catch (OperationCanceledException ex) { PodeHelpers.WriteException(ex, Listener, PodeLoggingLevel.Verbose); } // Handle cancellation. - catch (IOException ex) { PodeHelpers.WriteException(ex, Listener, PodeLoggingLevel.Verbose); } // Handle I/O exceptions. + catch (OperationCanceledException ex) { PodeLogger.WriteException(ex, Listener, PodeLoggingLevel.Verbose); } // Handle cancellation. + catch (IOException ex) { PodeLogger.WriteException(ex, Listener, PodeLoggingLevel.Verbose); } // Handle I/O exceptions. catch (AggregateException aex) { // Handle aggregated exceptions. @@ -258,8 +258,8 @@ private void ProcessAccept(SocketAsyncEventArgs args) { _ = Task.Run(async () => await StartReceive(accepted), Listener.CancellationToken).ConfigureAwait(false); } - catch (OperationCanceledException ex) { PodeHelpers.WriteException(ex, Listener, PodeLoggingLevel.Verbose); } - catch (IOException ex) { PodeHelpers.WriteException(ex, Listener, PodeLoggingLevel.Verbose); } + catch (OperationCanceledException ex) { PodeLogger.WriteException(ex, Listener, PodeLoggingLevel.Verbose); } + catch (IOException ex) { PodeLogger.WriteException(ex, Listener, PodeLoggingLevel.Verbose); } catch (AggregateException aex) { PodeHelpers.HandleAggregateException(aex, Listener, PodeLoggingLevel.Error, true); diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 6ac305834..daaf9ae32 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -2166,7 +2166,7 @@ function Get-PodeLoggingLevel { $Name ) - if (Test-PodeStandardLogger -Name $Name) { + if (Test-PodeLoggerEnabled -Name $Name) { return (Get-PodeLogger -Name $Name).Arguments.Levels } return @() diff --git a/tests/unit/Logging.Tests.ps1 b/tests/unit/Logging.Tests.ps1 index 8f7eff11d..9d5e30c69 100644 --- a/tests/unit/Logging.Tests.ps1 +++ b/tests/unit/Logging.Tests.ps1 @@ -58,6 +58,7 @@ Describe 'Write-PodeLog' { It 'Adds a log item' { Mock Test-PodeLoggerEnabled { return $true } + Mock Get-PodeLoggingLevel {return @('Informational')} Write-PodeLog -Name 'test' -InputObject 'test' [Pode.PodeLogger]::Count | Should -Be 1 From 1efbc898163327c0eeb032c61cfa3c4109f822ff Mon Sep 17 00:00:00 2001 From: mdaneri Date: Mon, 28 Oct 2024 08:59:41 -0700 Subject: [PATCH 21/53] File service fixes --- examples/Logging.ps1 | 5 +- src/Pode.psd1 | 4 +- src/Private/Logging.ps1 | 45 +++++------ src/Private/Server.ps1 | 4 +- src/Public/Logging.ps1 | 162 ++++++++++++---------------------------- 5 files changed, 79 insertions(+), 141 deletions(-) diff --git a/examples/Logging.ps1 b/examples/Logging.ps1 index 0bdbad2ca..85be29c4d 100644 --- a/examples/Logging.ps1 +++ b/examples/Logging.ps1 @@ -94,14 +94,15 @@ Start-PodeServer -browse { $requestLogging | Enable-PodeRequestLogging } - $logging | Enable-PodeErrorLogging -Raw:$Raw -Levels Error - $logging | Enable-PodeMainLogging -Raw:$Raw + New-PodeFileLoggingMethod -Name 'error' -MaxDays 4 -Format RFC5424 -ISO8601 | Enable-PodeErrorLogging -Raw -Levels Error + New-PodeFileLoggingMethod -Name 'default' -MaxDays 4 -Format Simple -ISO8601 | Enable-PodeDefaultLogging -Raw $logging | Add-PodeLogging -Name 'mylog' -Raw:$Raw Write-PodeLog -Name 'mylog' -Message 'just started' -Level 'Info' # GET request for web page on "localhost:8081/" Add-PodeRoute -Method Get -Path '/' -ScriptBlock { Write-PodeLog -Name 'mylog' -Message 'My custom log' -Level 'Info' + Write-PodeLog -Message "This is for the deafult log." Write-PodeViewResponse -Path 'simple' -Data @{ 'numbers' = @(1, 2, 3); } } diff --git a/src/Pode.psd1 b/src/Pode.psd1 index a8e6cc11e..ce5bf9ffc 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -291,11 +291,11 @@ 'New-PodeTerminalLoggingMethod' 'Enable-PodeRequestLogging', 'Enable-PodeErrorLogging', - 'Enable-PodeMainLogging', + 'Enable-PodeDefaultLogging', 'Add-PodeLogging', 'Disable-PodeRequestLogging', 'Disable-PodeErrorLogging', - 'Disable-PodeMainLogging', + 'Disable-PodeDefaultLogging', 'Remove-PodeLogging', 'Add-PodeLogger', 'Remove-PodeLogger', diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index 9736b39d7..6f6accd58 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -1,4 +1,6 @@ using namespace Pode + + <# .SYNOPSIS Defines the method for writing log messages to the terminal. @@ -143,7 +145,7 @@ function Get-PodeLoggingFileMethod { } } - # Write the item to the file + # Write the item to the file $outString | Out-File -FilePath $path -Encoding $Options.Encoding -Append -Force # Remove log files beyond the MaxDays retention period, ensuring this runs once a day @@ -632,10 +634,10 @@ function ConvertTo-PodeEventViewerLevel { Gets the script block for a specified inbuilt logging type. .DESCRIPTION -This function returns a script block that formats log entries for a specified inbuilt logging type in Pode. The supported types are 'Errors', 'Requests', 'General', and 'Main'. Each type has its own formatting logic. +This function returns a script block that formats log entries for a specified inbuilt logging type in Pode. The supported types are 'Errors', 'Requests', 'General', and 'Default'. Each type has its own formatting logic. .PARAMETER Type -The type of logging to get the script block for. Must be one of 'Errors', 'Requests', 'General', or 'Main'. +The type of logging to get the script block for. Must be one of 'Errors', 'Requests', 'General', or 'Default'. .EXAMPLE $script = Get-PodeLoggingInbuiltType -Type 'Requests' @@ -646,7 +648,7 @@ $script = Get-PodeLoggingInbuiltType -Type 'Errors' function Get-PodeLoggingInbuiltType { param( [Parameter(Mandatory = $true)] - [ValidateSet('Errors', 'Requests', 'General', 'Main', 'Listener')] + [ValidateSet('Errors', 'Requests', 'General', 'Default', 'Listener')] [string] $Type ) @@ -759,7 +761,7 @@ function Get-PodeLoggingInbuiltType { } } - 'main' { + 'Default' { $script = { param($item, $options) @@ -819,21 +821,22 @@ function Get-PodeErrorLoggingName { <# .SYNOPSIS -Gets the name of the Main logger. +Gets the name of the Default logger. .DESCRIPTION -This function returns the name of the logger used for logging Mains in Pode. +This function returns the name of the logger used for logging Defaults in Pode. .RETURNS -[string] - The name of the Main logger. +[string] - The name of the Default logger. .EXAMPLE -Get-PodeMainLoggingName +Get-PodeDefaultLoggingName #> -function Get-PodeMainLoggingName { - # Return the name of the Main logger - return '__pode_log_Mains__' +function Get-PodeDefaultLoggingName { + # Return the name of the Default logger + return '__pode_log_Defaults__' } + <# .SYNOPSIS Retrieves a Pode logger by name. @@ -1164,12 +1167,8 @@ function Start-PodeLoggerDispatcher { Write-PodeErrorLog -Exception $log.Item -Level 'Error' -ThreadId $log.Item.ThreadId } else { - if ($log.Item.Level -eq [Pode.PodeLoggingLevel]::Error) { - Write-PodeErrorLog -Message $log.Item.Message -ThreadId $log.Item.ThreadId -Tag 'Listener' - } - else { - Write-PodeErrorLog -Message $log.Item.Message -Level $log.Item.Level -ThreadId $log.Item.ThreadId -Tag 'Listener' - } + Write-PodeLog -Message $log.Item.Message -ThreadId $log.Item.ThreadId -Tag 'Listener' -Level $log.Item.Level + } continue } @@ -1401,7 +1400,7 @@ function Enable-PodeLoggingInternal { $Method, [Parameter(Mandatory = $true)] - [ValidateSet('Errors', 'Main' )] + [ValidateSet('Errors', 'Default' )] [string] $Type, @@ -1420,9 +1419,9 @@ function Enable-PodeLoggingInternal { $name = Get-PodeErrorLoggingName $scriptBlock = (Get-PodeLoggingInbuiltType -Type Errors) } - 'main' { - $name = Get-PodeMainLoggingName - $scriptBlock = (Get-PodeLoggingInbuiltType -Type Main) + 'Default' { + $name = Get-PodeDefaultLoggingName + $scriptBlock = (Get-PodeLoggingInbuiltType -Type Default) } } # error if it's already enabled @@ -1449,4 +1448,6 @@ function Enable-PodeLoggingInternal { $Method.ForEach({ $_.Logger += $name }) + return $PodeContext.Server.Logging.Type[$name] + } \ No newline at end of file diff --git a/src/Private/Server.ps1 b/src/Private/Server.ps1 index 59bc76cce..100f95f6e 100644 --- a/src/Private/Server.ps1 +++ b/src/Private/Server.ps1 @@ -147,7 +147,7 @@ function Start-PodeInternalServer { # run running event hooks Invoke-PodeEvent -Type Running - Write-PodeErrorLog -Message "Pode $(Get-PodeVersion) (PID: $($PID))" -Level Info + Write-PodeLog -Message "Pode $(Get-PodeVersion) (PID: $($PID))" -Level Info # state what endpoints are being listened on if ($endpoints.Length -gt 0) { @@ -173,7 +173,7 @@ function Start-PodeInternalServer { $urlAndFlags += "$($_.Url) $($flags)" } - Write-PodeErrorLog -Message "$msg - $($urlAndFlags -join ' , ')" -Level Info + Write-PodeLog -Message "$msg - $($urlAndFlags -join ' , ')" -Level Info # state the OpenAPI endpoints for each definition foreach ($key in $PodeContext.Server.OpenAPI.Definitions.keys) { diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index daaf9ae32..84ecbd46f 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -119,6 +119,9 @@ function New-PodeTerminalLoggingMethod { .PARAMETER DataFormat The custom date format for log entries. Mutually exclusive with ISO8601. +.PARAMETER Encoding + The encoding to use for Syslog messages. Supported values are ASCII, BigEndianUnicode, Default, Unicode, UTF32, UTF7, and UTF8. Defaults to UTF8. + .PARAMETER ISO8601 If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. @@ -189,6 +192,11 @@ function New-PodeFileLoggingMethod { [string] $DataFormat, + [Parameter()] + [ValidateSet('ASCII', 'BigEndianUnicode', 'Default', 'Unicode', 'UTF32', 'UTF7', 'UTF8')] + [string] + $Encoding = 'UTF8', + [Parameter(ParameterSetName = 'ISO8601')] [switch] $ISO8601, @@ -213,8 +221,9 @@ function New-PodeFileLoggingMethod { # Resolve the log file path $Path = (Protect-PodeValue -Value $Path -Default './logs') $Path = (Get-PodeRelativePath -Path $Path -JoinRoot -Resolve) - $null = New-Item -Path $Path -ItemType Directory -Force - + if (! (Test-Path -Path $Path -PathType Leaf)) { + $null = New-Item -Path $Path -ItemType Directory -Force + } # Create a unique ID for this logging method $methodId = New-PodeGuid @@ -1132,6 +1141,9 @@ function Enable-PodeRequestLogging { .PARAMETER Raw If supplied, the log item returned will be the raw Error item as a hashtable and not a string (for Custom methods). +.PARAMETER DisableDefaultLog + If supplied, the error logs will NOT be duplicated to the default logging method. + .EXAMPLE New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging #> @@ -1149,22 +1161,14 @@ function Enable-PodeErrorLogging { $Levels = @('Error'), [switch] - $Raw + $Raw, + + [switch] + $DisableDefaultLog ) begin { $pipelineMethods = @() - - <# $name = Get-PodeErrorLoggingName - # error if it's already enabled - if ($PodeContext.Server.Logging.Type.Contains($Name)) { - # Error Logging has already been enabled - throw ($PodeLocale.loggingAlreadyEnabledExceptionMessage -f 'Error') - } - # all errors? - if ($Levels -contains '*') { - $Levels = @('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Verbose', 'Debug') - }#> } process { @@ -1182,43 +1186,34 @@ function Enable-PodeErrorLogging { $Method = $pipelineMethods } - <# # add the error logger - $PodeContext.Server.Logging.Type[$name] = @{ - Method = $Method - ScriptBlock = (Get-PodeLoggingInbuiltType -Type Errors) - Arguments = @{ - Raw = $Raw - Levels = $Levels - DataFormat = $Method.Arguments.DataFormat - } - Standard = $true - } -#> - Enable-PodeLoggingInternal -Method $Method -Type Errors -Levels $Levels -Raw:$Raw + $logging = Enable-PodeLoggingInternal -Method $Method -Type Errors -Levels $Levels -Raw:$Raw + + + $logging.DuplicateToDefaultLog = ! $DisableDefaultLog.IsPresent $Method.ForEach({ $_.Logger += $name }) } } <# .SYNOPSIS - Enables Main Logging using a supplied output method. + Enables Default Logging using a supplied output method. .DESCRIPTION - Enables Main Logging using a supplied output method. + Enables Default Logging using a supplied output method. .PARAMETER Method The Method to use for output the log entry (From New-PodeLoggingMethod). .PARAMETER Levels - The Levels of Mains that should be logged (default is 'Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational'). + The Levels that should be logged (default is 'Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational'). .PARAMETER Raw - If supplied, the log item returned will be the raw Main item as a hashtable and not a string (for Custom methods). + If supplied, the log item returned will be the raw Default item as a hashtable and not a string (for Custom methods). .EXAMPLE - New-PodeLoggingMethod -Terminal | Enable-PodeMainLogging + New-PodeLoggingMethod -Terminal | Enable-PodeDefaultLogging #> -function Enable-PodeMainLogging { +function Enable-PodeDefaultLogging { [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] @@ -1253,7 +1248,7 @@ function Enable-PodeMainLogging { if ($pipelineMethods.Count -gt 1) { $Method = $pipelineMethods } - Enable-PodeLoggingInternal -Method $Method -Type Main -Levels $Levels -Raw:$Raw + Enable-PodeLoggingInternal -Method $Method -Type Default -Levels $Levels -Raw:$Raw $Method.ForEach({ $_.Logger += $name }) } } @@ -1405,19 +1400,19 @@ function Disable-PodeErrorLogging { <# .SYNOPSIS -Disables Main Logging. +Disables Default Logging. .DESCRIPTION -Disables Main Logging. +Disables Default Logging. .EXAMPLE -Disable-PodeMainLogging +Disable-PodeDefaultLogging #> -function Disable-PodeMainLogging { +function Disable-PodeDefaultLogging { [CmdletBinding()] param() - Remove-PodeLogger -Name (Get-PodeMainLoggingName) + Remove-PodeLogger -Name (Get-PodeDefaultLoggingName) } @@ -1441,7 +1436,7 @@ function Disable-PodeMainLogging { An array of arguments to supply to the Custom Logger's ScriptBlock. .EXAMPLE - New-PodeLoggingMethod -Terminal | Add-PodeLogger -Name 'Main' -ScriptBlock { /* logic */ } + New-PodeLoggingMethod -Terminal | Add-PodeLogger -Name 'Default' -ScriptBlock { /* logic */ } #> function Add-PodeLogger { [CmdletBinding()] @@ -1669,73 +1664,11 @@ function Write-PodeErrorLog { ) Process { - - Write-PodeLog @PSBoundParameters -name (Get-PodeErrorLoggingName) -SuppressErrorLog - - <# - # Check if logging is enabled and the error level is valid $name = Get-PodeErrorLoggingName - if (!(Test-PodeLoggerEnabled -Name $name)) { return } - - $levels = @(Get-PodeErrorLoggingLevel) - if ($levels -inotcontains $Level) { return } - - # Build the error object - switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { - 'exception' { - $item = @{ - Category = $Exception.Source - Message = $Exception.Message - StackTrace = $Exception.StackTrace - Tag = $Tag - } - } - 'errorrecord' { - $item = @{ - Category = $ErrorRecord.CategoryInfo.ToString() - Message = $ErrorRecord.Exception.Message - StackTrace = $ErrorRecord.ScriptStackTrace - Tag = $Tag - } - } - 'message' { - $item = @{ - Category = $Category.ToString() - Message = $Message - Tag = $Tag - } - } + Write-PodeLog @PSBoundParameters -name $name -SuppressErrorLog + if ($PodeContext.Server.Logging.Type[$name].DuplicateToDefaultLog) { + Write-PodeLog @PSBoundParameters -name (Get-PodeDefaultLoggingName) -SuppressErrorLog } - - # General info and thread id - $item['Server'] = $PodeContext.Server.ComputerName - $item['Level'] = $Level - $item['Date'] = if ($PodeContext.Server.Logging.Type[$Name].Method.Arguments.AsUTC) { - [datetime]::UtcNow - } - else { - [datetime]::Now - } - - $item['ThreadId'] = if ($ThreadId) { - $ThreadId - } - else { - [System.Threading.Thread]::CurrentThread.ManagedThreadId - } - - # Add the item to the logger queue - $logItem = @{ - Name = $name - Item = $item - } - [Pode.PodeLogger]::Enqueue($logItem) - - # Log inner exceptions if specified - if ($CheckInnerException -and ($null -ne $Exception.InnerException) -and ![string]::IsNullOrWhiteSpace($Exception.InnerException.Message)) { - $Exception.InnerException | Write-PodeErrorLog -Level $Level -ThreadId $ThreadId - } -#> } @@ -1802,7 +1735,7 @@ function Write-PodeErrorLog { function Write-PodeLog { [CmdletBinding(DefaultParameterSetName = 'Message')] param( - [Parameter(Mandatory = $true)] + [Parameter()] [string] $Name, @@ -1848,6 +1781,14 @@ function Write-PodeLog { $SuppressErrorLog ) + begin { + if (!$Name) { + $Name = Get-PodeDefaultLoggingName + } + + # Get the configured log method. + $log = $PodeContext.Server.Logging.Type[$Name] + } Process { # Define the log item based on the selected parameter set. switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { @@ -1886,7 +1827,7 @@ function Write-PodeLog { Tag = $Tag } } - + break } 'errorrecord' { if (!$Level) { $Level = 'Error' } @@ -1899,12 +1840,9 @@ function Write-PodeLog { Tag = $Tag } } + break } } - - # Get the configured log method. - $log = $PodeContext.Server.Logging.Type[$Name] - if ($log.Standard) { # Add server details to the log item. $logItem.Item.Server = $PodeContext.Server.ComputerName @@ -1918,7 +1856,6 @@ function Write-PodeLog { # If error logging is not suppressed, log errors or exceptions. if ((! $SuppressErrorLog.IsPresent) -and (Test-PodeErrorLoggingEnabled)) { if ($PSCmdlet.ParameterSetName.ToLowerInvariant() -eq 'exception') { - [Pode.PodeLogger]::Enqueue( @{ Name = Get-PodeErrorLoggingName Item = @{ @@ -1933,7 +1870,6 @@ function Write-PodeLog { } }) - Write-PodeErrorLog -Exception $Exception -Level $Level -CheckInnerException:$CheckInnerException -ThreadId $ThreadId } elseif ($PSCmdlet.ParameterSetName.ToLowerInvariant() -eq 'errorrecord') { [Pode.PodeLogger]::Enqueue( @{ From f9ab92009b0cbb362211d60fb2eacc6798b71474 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Mon, 28 Oct 2024 09:18:10 -0700 Subject: [PATCH 22/53] fix test and file logging --- src/Private/FileWatchers.ps1 | 2 +- src/Private/Helpers.ps1 | 61 +-------------------------- src/Private/Logging.ps1 | 64 ++++++++++++++++++++++++++++- src/Public/Caching.ps1 | 2 +- src/Public/OAComponents.ps1 | 2 +- tests/unit/Authentication.Tests.ps1 | 3 +- tests/unit/Cookies.Tests.ps1 | 1 - tests/unit/CronParser.Tests.ps1 | 1 - tests/unit/Cryptography.Tests.ps1 | 1 - tests/unit/Endware.Tests.ps1 | 1 - tests/unit/Flash.Tests.ps1 | 1 - tests/unit/Headers.Tests.ps1 | 1 - tests/unit/Helpers.Tests.ps1 | 1 - tests/unit/Mappers.Tests.ps1 | 1 - tests/unit/Middleware.Tests.ps1 | 1 - tests/unit/NameGenerator.Tests.ps1 | 1 - tests/unit/OpenApi.Tests.ps1 | 1 - tests/unit/PrivateOpenApi.Tests.ps1 | 1 - tests/unit/Schedules.Tests.ps1 | 1 - tests/unit/Server.Tests.ps1 | 1 + 20 files changed, 68 insertions(+), 80 deletions(-) diff --git a/src/Private/FileWatchers.ps1 b/src/Private/FileWatchers.ps1 index ce0ebc4c7..9a1bfb3e9 100644 --- a/src/Private/FileWatchers.ps1 +++ b/src/Private/FileWatchers.ps1 @@ -162,4 +162,4 @@ function Start-PodeFileWatcherRunspace { } Add-PodeRunspace -Type Files -Name 'KeepAlive' -ScriptBlock $waitScript -Parameters @{ 'Watcher' = $watcher } -NoProfile -} +} \ No newline at end of file diff --git a/src/Private/Helpers.ps1 b/src/Private/Helpers.ps1 index f64463387..80b8213b9 100644 --- a/src/Private/Helpers.ps1 +++ b/src/Private/Helpers.ps1 @@ -3821,63 +3821,4 @@ function Copy-PodeObjectDeepClone { return [System.Management.Automation.PSSerializer]::Deserialize($xmlSerializer) } } - -<# -.SYNOPSIS - Handles failure actions based on the provided parameters. - -.DESCRIPTION - This function processes failure scenarios by either ignoring the failure, - reporting it on the console and continuing, or reporting it on the console - and halting the server. The behavior is controlled by the 'FailureAction' - parameter. - -.PARAMETER Message - The message to be displayed in case of a failure. - -.PARAMETER FailureAction - Specifies the action to take in case of failure. Accepted values are: - - 'Ignore': Do nothing and continue execution. - - 'Report': Display the message on the console and continue execution. - - 'Halt': Display the message on the console and halt the server. - -.EXAMPLE - Invoke-PodeHandleFailure -Message "An error occurred." -FailureAction "Report" - This will display the message "An error occurred." on the console and continue execution. - -.EXAMPLE - Invoke-PodeHandleFailure -Message "Critical failure." -FailureAction "Halt" - This will display the message "Critical failure." on the console and halt the server. - -.NOTES - This is an internal function and may change in future releases of Pode. -#> -function Invoke-PodeHandleFailure { - param( - [Parameter(Mandatory = $true)] - [string] - $Message, - - [Parameter(Mandatory = $true)] - [string] - [ValidateSet('Ignore', 'Report', 'Halt' )] - $FailureAction - - ) - switch ($FailureAction.ToLowerInvariant()) { - 'ignore' { - # Do nothing and continue - } - 'report' { - # Report on console and continue - Write-PodeHost $Message -ForegroundColor Yellow - } - 'halt' { - # Report on console and halt - Write-PodeHost $Message -ForegroundColor Red - Write-PodeHost 'Pode Server shutting down.' -ForegroundColor Red - Close-PodeServer - } - } -} - + \ No newline at end of file diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index 6f6accd58..eded1bb69 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -145,7 +145,7 @@ function Get-PodeLoggingFileMethod { } } - # Write the item to the file + # Write the item to the file $outString | Out-File -FilePath $path -Encoding $Options.Encoding -Append -Force # Remove log files beyond the MaxDays retention period, ensuring this runs once a day @@ -1450,4 +1450,64 @@ function Enable-PodeLoggingInternal { return $PodeContext.Server.Logging.Type[$name] -} \ No newline at end of file +} + +<# +.SYNOPSIS + Handles failure actions based on the provided parameters. + +.DESCRIPTION + This function processes failure scenarios by either ignoring the failure, + reporting it on the console and continuing, or reporting it on the console + and halting the server. The behavior is controlled by the 'FailureAction' + parameter. + +.PARAMETER Message + The message to be displayed in case of a failure. + +.PARAMETER FailureAction + Specifies the action to take in case of failure. Accepted values are: + - 'Ignore': Do nothing and continue execution. + - 'Report': Display the message on the console and continue execution. + - 'Halt': Display the message on the console and halt the server. + +.EXAMPLE + Invoke-PodeHandleFailure -Message "An error occurred." -FailureAction "Report" + This will display the message "An error occurred." on the console and continue execution. + +.EXAMPLE + Invoke-PodeHandleFailure -Message "Critical failure." -FailureAction "Halt" + This will display the message "Critical failure." on the console and halt the server. + +.NOTES + This is an internal function and may change in future releases of Pode. +#> +function Invoke-PodeHandleFailure { + param( + [Parameter(Mandatory = $true)] + [string] + $Message, + + [Parameter(Mandatory = $true)] + [string] + [ValidateSet('Ignore', 'Report', 'Halt' )] + $FailureAction + + ) + switch ($FailureAction.ToLowerInvariant()) { + 'ignore' { + # Do nothing and continue + } + 'report' { + # Report on console and continue + Write-PodeHost $Message -ForegroundColor Yellow + } + 'halt' { + # Report on console and halt + Write-PodeHost $Message -ForegroundColor Red + Write-PodeHost 'Pode Server shutting down.' -ForegroundColor Red + Close-PodeServer + } + } +} + diff --git a/src/Public/Caching.ps1 b/src/Public/Caching.ps1 index f40892efc..93f7cef5b 100644 --- a/src/Public/Caching.ps1 +++ b/src/Public/Caching.ps1 @@ -554,4 +554,4 @@ function Get-PodeCacheDefaultTtl { param() return $PodeContext.Server.Cache.DefaultTtl -} \ No newline at end of file +} diff --git a/src/Public/OAComponents.ps1 b/src/Public/OAComponents.ps1 index e65b18912..a829f5274 100644 --- a/src/Public/OAComponents.ps1 +++ b/src/Public/OAComponents.ps1 @@ -962,4 +962,4 @@ if (!(Test-Path Alias:Enable-PodeOA)) { if (!(Test-Path Alias:Get-PodeOpenApiDefinition)) { New-Alias Get-PodeOpenApiDefinition -Value Get-PodeOADefinition -} \ No newline at end of file +} diff --git a/tests/unit/Authentication.Tests.ps1 b/tests/unit/Authentication.Tests.ps1 index 18fc79b4c..985f6b9fb 100644 --- a/tests/unit/Authentication.Tests.ps1 +++ b/tests/unit/Authentication.Tests.ps1 @@ -5,7 +5,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - } $now = [datetime]::UtcNow @@ -191,4 +190,4 @@ Describe 'Expand-PodeAuthMerge Tests' { { Expand-PodeAuthMerge -Names @('NonExistentAuth') } | Should -Throw } -} +} \ No newline at end of file diff --git a/tests/unit/Cookies.Tests.ps1 b/tests/unit/Cookies.Tests.ps1 index 070ea84c4..294901e9f 100644 --- a/tests/unit/Cookies.Tests.ps1 +++ b/tests/unit/Cookies.Tests.ps1 @@ -5,7 +5,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - } Describe 'Test-PodeCookie' { It 'Returns true' { diff --git a/tests/unit/CronParser.Tests.ps1 b/tests/unit/CronParser.Tests.ps1 index 42cb24332..5692d6324 100644 --- a/tests/unit/CronParser.Tests.ps1 +++ b/tests/unit/CronParser.Tests.ps1 @@ -5,7 +5,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - } Describe 'Get-PodeCronField' { diff --git a/tests/unit/Cryptography.Tests.ps1 b/tests/unit/Cryptography.Tests.ps1 index 92c4bdae3..423446f27 100644 --- a/tests/unit/Cryptography.Tests.ps1 +++ b/tests/unit/Cryptography.Tests.ps1 @@ -3,7 +3,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - } Describe 'Invoke-PodeHMACSHA256Hash' { diff --git a/tests/unit/Endware.Tests.ps1 b/tests/unit/Endware.Tests.ps1 index e556563e3..c3e80ae0c 100644 --- a/tests/unit/Endware.Tests.ps1 +++ b/tests/unit/Endware.Tests.ps1 @@ -5,7 +5,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - } Describe 'Invoke-PodeEndware' { diff --git a/tests/unit/Flash.Tests.ps1 b/tests/unit/Flash.Tests.ps1 index 2f59fc3d1..3996553c2 100644 --- a/tests/unit/Flash.Tests.ps1 +++ b/tests/unit/Flash.Tests.ps1 @@ -6,7 +6,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - } Describe 'Add-PodeFlashMessage' { diff --git a/tests/unit/Headers.Tests.ps1 b/tests/unit/Headers.Tests.ps1 index c3fe90afe..a666ce642 100644 --- a/tests/unit/Headers.Tests.ps1 +++ b/tests/unit/Headers.Tests.ps1 @@ -5,7 +5,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - } Describe 'Test-PodeHeader' { Context 'WebServer' { diff --git a/tests/unit/Helpers.Tests.ps1 b/tests/unit/Helpers.Tests.ps1 index 67a1c842f..084d98efb 100644 --- a/tests/unit/Helpers.Tests.ps1 +++ b/tests/unit/Helpers.Tests.ps1 @@ -7,7 +7,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - } Describe 'Get-PodeType' { diff --git a/tests/unit/Mappers.Tests.ps1 b/tests/unit/Mappers.Tests.ps1 index 71054e904..ab0446730 100644 --- a/tests/unit/Mappers.Tests.ps1 +++ b/tests/unit/Mappers.Tests.ps1 @@ -5,7 +5,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - } Describe 'Get-PodeContentType' { Context 'No extension supplied' { diff --git a/tests/unit/Middleware.Tests.ps1 b/tests/unit/Middleware.Tests.ps1 index e3af190d6..62c5ff9ec 100644 --- a/tests/unit/Middleware.Tests.ps1 +++ b/tests/unit/Middleware.Tests.ps1 @@ -7,7 +7,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - } Describe 'Get-PodeInbuiltMiddleware' { diff --git a/tests/unit/NameGenerator.Tests.ps1 b/tests/unit/NameGenerator.Tests.ps1 index 4844b2a06..85a78f316 100644 --- a/tests/unit/NameGenerator.Tests.ps1 +++ b/tests/unit/NameGenerator.Tests.ps1 @@ -3,7 +3,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - } Describe 'Get-PodeRandomName' { diff --git a/tests/unit/OpenApi.Tests.ps1 b/tests/unit/OpenApi.Tests.ps1 index e3a10c564..b62b29b89 100644 --- a/tests/unit/OpenApi.Tests.ps1 +++ b/tests/unit/OpenApi.Tests.ps1 @@ -6,7 +6,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - } Describe 'OpenApi' { diff --git a/tests/unit/PrivateOpenApi.Tests.ps1 b/tests/unit/PrivateOpenApi.Tests.ps1 index ce56a1f7e..ec822fcab 100644 --- a/tests/unit/PrivateOpenApi.Tests.ps1 +++ b/tests/unit/PrivateOpenApi.Tests.ps1 @@ -6,7 +6,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - } Describe 'PrivateOpenApi' { diff --git a/tests/unit/Schedules.Tests.ps1 b/tests/unit/Schedules.Tests.ps1 index 1488574c7..ce426bbe0 100644 --- a/tests/unit/Schedules.Tests.ps1 +++ b/tests/unit/Schedules.Tests.ps1 @@ -7,7 +7,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - } Describe 'Find-PodeSchedule' { Context 'Invalid parameters supplied' { diff --git a/tests/unit/Server.Tests.ps1 b/tests/unit/Server.Tests.ps1 index d10ac4640..faef879ff 100644 --- a/tests/unit/Server.Tests.ps1 +++ b/tests/unit/Server.Tests.ps1 @@ -40,6 +40,7 @@ Describe 'Start-PodeInternalServer' { Mock Add-PodeScopedVariablesInbuilt { } Mock Write-PodeHost { } Mock Write-PodeErrorLog { } + Mock Write-PodeLog { } } It 'Calls one-off script logic' { From da1c605f63abdaf3d0a8e1bc1f8c714b2a865387 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Mon, 28 Oct 2024 09:31:18 -0700 Subject: [PATCH 23/53] fix servless test --- tests/unit/Context.Tests.ps1 | 1 - tests/unit/Handlers.Tests.ps1 | 1 - tests/unit/Metrics.Tests.ps1 | 1 - tests/unit/Responses.Tests.ps1 | 1 - tests/unit/Routes.Tests.ps1 | 1 - tests/unit/Serverless.Tests.ps1 | 9 +++++++-- tests/unit/Sessions.Tests.ps1 | 1 - tests/unit/State.Tests.ps1 | 1 - 8 files changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/unit/Context.Tests.ps1 b/tests/unit/Context.Tests.ps1 index f9185885f..6006e6428 100644 --- a/tests/unit/Context.Tests.ps1 +++ b/tests/unit/Context.Tests.ps1 @@ -5,7 +5,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - $PodeContext = @{ 'Server' = $null; } } diff --git a/tests/unit/Handlers.Tests.ps1 b/tests/unit/Handlers.Tests.ps1 index ccb9d1256..29dd6ce09 100644 --- a/tests/unit/Handlers.Tests.ps1 +++ b/tests/unit/Handlers.Tests.ps1 @@ -7,7 +7,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - $PodeContext = @{ 'Server' = $null; } } diff --git a/tests/unit/Metrics.Tests.ps1 b/tests/unit/Metrics.Tests.ps1 index 3e799aab8..5dcbe9b05 100644 --- a/tests/unit/Metrics.Tests.ps1 +++ b/tests/unit/Metrics.Tests.ps1 @@ -6,7 +6,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - $PodeContext = @{ Metrics = @{ Server = @{ diff --git a/tests/unit/Responses.Tests.ps1 b/tests/unit/Responses.Tests.ps1 index f02e165e5..80a10c741 100644 --- a/tests/unit/Responses.Tests.ps1 +++ b/tests/unit/Responses.Tests.ps1 @@ -7,7 +7,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - } Describe 'Set-PodeResponseStatus' { diff --git a/tests/unit/Routes.Tests.ps1 b/tests/unit/Routes.Tests.ps1 index 4a1c2e4bb..b5f746448 100644 --- a/tests/unit/Routes.Tests.ps1 +++ b/tests/unit/Routes.Tests.ps1 @@ -7,7 +7,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - $PodeContext = @{ 'Server' = $null; } } diff --git a/tests/unit/Serverless.Tests.ps1 b/tests/unit/Serverless.Tests.ps1 index 5092d10d4..4509889f3 100644 --- a/tests/unit/Serverless.Tests.ps1 +++ b/tests/unit/Serverless.Tests.ps1 @@ -6,7 +6,6 @@ BeforeAll { $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests[\\/]unit', '/src/' Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - Mock Test-PodeLoggerEnabled { return $false } } Describe 'Start-PodeAzFuncServer' { @@ -27,6 +26,8 @@ Describe 'Start-PodeAzFuncServer' { Mock Set-PodeServerHeader { } Mock Set-PodeResponseStatus { } Mock Update-PodeServerRequestMetric { } + Mock Write-PodeLog {} + Mock Write-PodeErrorLog {} } It 'Throws error for null data' { { Start-PodeAzFuncServer -Data $null } | Should -Throw -ErrorId 'ParameterArgumentValidationErrorNullNotAllowed,Start-PodeAzFuncServer' @@ -157,7 +158,11 @@ Describe 'Start-PodeAwsLambdaServer' { Mock Invoke-PodeEndware { } Mock Set-PodeServerHeader { } Mock Set-PodeResponseStatus { } - Mock Update-PodeServerRequestMetric { } } + Mock Update-PodeServerRequestMetric { } + Mock Write-PodeLog {} + Mock Write-PodeErrorLog {} + + } It 'Throws error for null data' { { Start-PodeAwsLambdaServer -Data $null } | Should -Throw -ErrorId 'ParameterArgumentValidationErrorNullNotAllowed,Start-PodeAwsLambdaServer' diff --git a/tests/unit/Sessions.Tests.ps1 b/tests/unit/Sessions.Tests.ps1 index 239803ce1..dcf9a77b6 100644 --- a/tests/unit/Sessions.Tests.ps1 +++ b/tests/unit/Sessions.Tests.ps1 @@ -7,7 +7,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - $now = [datetime]::UtcNow } diff --git a/tests/unit/State.Tests.ps1 b/tests/unit/State.Tests.ps1 index 56580e5e8..f8c2ff61b 100644 --- a/tests/unit/State.Tests.ps1 +++ b/tests/unit/State.Tests.ps1 @@ -7,7 +7,6 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' - $PodeContext = @{ 'Server' = $null; } } From af602322710bdf3ac43531ef3ce140bc580dcdac Mon Sep 17 00:00:00 2001 From: mdaneri Date: Fri, 1 Nov 2024 09:04:03 -0700 Subject: [PATCH 24/53] merging tag with source --- examples/Logging.ps1 | 9 +++-- src/Private/Logging.ps1 | 79 +++++++++++++++-------------------------- src/Public/Logging.ps1 | 69 ++++++++++++++++++++++++++++------- 3 files changed, 91 insertions(+), 66 deletions(-) diff --git a/examples/Logging.ps1 b/examples/Logging.ps1 index 85be29c4d..9086e83d9 100644 --- a/examples/Logging.ps1 +++ b/examples/Logging.ps1 @@ -84,7 +84,7 @@ Start-PodeServer -browse { } if ( $LoggingType -icontains 'syslog') { - $logging += New-PodeSyslogLoggingMethod -Server 127.0.0.1 -Transport UDP -AsUTC -ISO8601 -FailureAction Report + $logging += New-PodeSyslogLoggingMethod -Server 127.0.0.1 -Transport UDP -AsUTC -ISO8601 -FailureAction Report } if ($logging.Count -eq 0) { @@ -95,14 +95,17 @@ Start-PodeServer -browse { } New-PodeFileLoggingMethod -Name 'error' -MaxDays 4 -Format RFC5424 -ISO8601 | Enable-PodeErrorLogging -Raw -Levels Error - New-PodeFileLoggingMethod -Name 'default' -MaxDays 4 -Format Simple -ISO8601 | Enable-PodeDefaultLogging -Raw + @( + (New-PodeFileLoggingMethod -Name 'default' -MaxDays 4 -Format Simple -ISO8601) + (New-PodeSyslogLoggingMethod -Server 127.0.0.1 -Transport UDP -AsUTC -ISO8601 -SyslogProtocol RFC3164 -FailureAction Report -DefaultTag 'test') + ) | Enable-PodeDefaultLogging -Raw $logging | Add-PodeLogging -Name 'mylog' -Raw:$Raw Write-PodeLog -Name 'mylog' -Message 'just started' -Level 'Info' # GET request for web page on "localhost:8081/" Add-PodeRoute -Method Get -Path '/' -ScriptBlock { Write-PodeLog -Name 'mylog' -Message 'My custom log' -Level 'Info' - Write-PodeLog -Message "This is for the deafult log." + Write-PodeLog -Message 'This is for the deafult log.' Write-PodeViewResponse -Path 'simple' -Data @{ 'numbers' = @(1, 2, 3); } } diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index eded1bb69..a0b61acd9 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -206,7 +206,7 @@ function ConvertTo-PodeSyslogFormat { $message = ($RawItem.Message | Protect-PodeLogItem) } } - +#write-podehost $RawItem -Explode # Map $Level to syslog severity switch ($RawItem.Level) { 'emergency' { $severity = 0; break } @@ -225,6 +225,7 @@ function ConvertTo-PodeSyslogFormat { $facility = 1 # User-level messages $priority = ($facility * 8) + $severity + $processId = $PID # Determine the syslog message format switch ($RFC) { 'RFC3164' { @@ -232,15 +233,14 @@ function ConvertTo-PodeSyslogFormat { $MaxLength = 1024 # Assemble the full syslog formatted message $timestamp = $RawItem.Date.ToString('MMM dd HH:mm:ss') - $fullSyslogMessage = "<$priority>$timestamp $($PodeContext.Server.ComputerName) $Source[$processId]: $message" + $fullSyslogMessage = "<$priority>$timestamp $($PodeContext.Server.ComputerName) $($RawItem.Tag): $message" break } 'RFC5424' { - $processId = $PID $timestamp = $RawItem.Date.ToString('yyyy-MM-ddTHH:mm:ss.ffffffK') # Assemble the full syslog formatted message - $fullSyslogMessage = "<$priority>1 $timestamp $($PodeContext.Server.ComputerName) $Source $processId - - $message" + $fullSyslogMessage = "<$priority>1 $timestamp $($PodeContext.Server.ComputerName) $($RawItem.Tag) $processId - - $message" # Set the max message length per RFC 5424 section 6.1 $MaxLength = 2048 @@ -268,6 +268,7 @@ function ConvertTo-PodeSyslogFormat { if ($MaxLength -gt 0 -and $fullSyslogMessage.Length -gt $MaxLength) { return $fullSyslogMessage.Substring(0, $MaxLength) } + write-podehost $fullSyslogMessage # Return the full syslog formatted message return $fullSyslogMessage } @@ -424,7 +425,7 @@ function Get-PodeLoggingRestfulMethod { return { param($MethodId) - $log = @{} + $log = @{ } while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { Start-Sleep -Milliseconds 100 @@ -435,92 +436,69 @@ function Get-PodeLoggingRestfulMethod { $RawItem = $log.RawItem # Ensure item and rawItem are arrays - if ($Item -isnot [array]) { - $Item = @($Item) - } + $Item = @($Item) + $RawItem = @($RawItem) - if ($RawItem -isnot [array]) { - $RawItem = @($RawItem) - } - - # Determine the transport protocol and send the message + # Determine the platform and send the message switch ($Options.Platform) { 'Splunk' { - # Construct the Splunk API URL $url = "$($Options.BaseUrl)/services/collector" - - # Set the headers for Splunk $headers = @{ 'Authorization' = "Splunk $($Options.Token)" 'Content-Type' = 'application/json' } - $items = @() - for ($i = 0; $i -lt $Item.Length; $i++) { - # Mask values - $message = ($Item[$i] | Protect-PodeLogItem) - if ([string]::IsNullOrWhiteSpace($RawItem[$i].Level)) { - $severity = 'INFO' - } - else { - $severity = $RawItem[$i].Level.ToUpperInvariant() - } - $items += ConvertTo-Json -Compress -InputObject @{ - event = $message - host = $PodeContext.Server.ComputerName - source = $Options.source - time = [math]::Round(($RawItem[$i].Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalSeconds) - fields = @{ - severity = $severity + $items = $Item | ForEach-Object { + @{ + event = ($_ | Protect-PodeLogItem) + host = $PodeContext.Server.ComputerName + source = $Options.source + sourcetype = $RawItem.Tag + time = [math]::Round(($RawItem.Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalSeconds) + fields = @{ + severity = $RawItem.Level.ToUpperInvariant() } } } + $body = $items | ConvertTo-Json -Compress - $body = $items -join ' ' - - # Send the message to Splunk try { Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -SkipCertificateCheck:$Options.SkipCertificateCheck } catch { Invoke-PodeHandleFailure -Message "Failed to send log to Splunk: $_" -FailureAction $Options.FailureAction } - break } 'LogInsight' { - # Construct the Log Insight API URL $url = "$($Options.BaseUrl)/api/v1/messages/ingest/$($Options.Id)" - - # Set the headers for Log Insight $headers = @{ 'Content-Type' = 'application/json' } - $messages = @() - for ($i = 0; $i -lt $Item.Length; $i++) { - $messages += @{ - text = ($Item[$i] | Protect-PodeLogItem) - timestamp = [math]::Round(($RawItem[$i].Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalSeconds) + + $messages = $Item | ForEach-Object { + @{ + text = ($_ | Protect-PodeLogItem) + timestamp = [math]::Round(($RawItem.Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalMilliseconds) + fields = @{ + severity = $RawItem.Level.ToUpperInvariant() + tag = $RawItem.Tag + } } } - # Define the message payload $payload = @{ messages = $messages } - - # Convert payload to JSON $body = $payload | ConvertTo-Json -Compress - # Send the message to Log Insight try { Invoke-RestMethod -Uri $url -Method Post -Body $body -Headers $headers -SkipCertificateCheck:$Options.SkipCertificateCheck } catch { Invoke-PodeHandleFailure -Message "Failed to send log to LogInsight: $_" -FailureAction $Options.FailureAction } - break } } @@ -528,6 +506,7 @@ function Get-PodeLoggingRestfulMethod { } } } + } <# diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 84ecbd46f..21ed3f417 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -16,6 +16,9 @@ .PARAMETER AsUTC If set, the time will be logged in UTC instead of local time. +.PARAMETER DefaultTag + The tag to use if none is specified on the log entry. Defaults to '-'. + .OUTPUTS Hashtable: Returns a hashtable containing the logging method configuration. @@ -46,7 +49,11 @@ function New-PodeTerminalLoggingMethod { [Parameter()] [switch] - $AsUTC + $AsUTC, + + [Parameter()] + [string] + $DefaultTag = '-' ) # Determine the date format based on parameter set @@ -78,6 +85,7 @@ function New-PodeTerminalLoggingMethod { Arguments = @{ DataFormat = $DataFormat AsUTC = $AsUTC.IsPresent + DefaultTag = $DefaultTag } } } @@ -128,6 +136,9 @@ function New-PodeTerminalLoggingMethod { .PARAMETER AsUTC If set, logs the time in UTC instead of the local time. +.PARAMETER DefaultTag + The tag to use if none is specified on the log entry. Defaults to '-'. + .OUTPUTS Hashtable: Returns a hashtable containing the logging method configuration. @@ -203,7 +214,11 @@ function New-PodeFileLoggingMethod { [Parameter()] [switch] - $AsUTC + $AsUTC, + + [Parameter()] + [string] + $DefaultTag = '-' ) # Determine the date format based on the parameter set @@ -254,6 +269,7 @@ function New-PodeFileLoggingMethod { MaxLength = $MaxLength Source = $Source Separator = $Separator + DefaultTag = $DefaultTag } } } @@ -373,6 +389,7 @@ function New-PodeEventViewerLoggingMethod { FailureAction = $FailureAction DataFormat = $DataFormat AsUTC = $AsUTC.IsPresent + Tag = $Source } } } @@ -420,6 +437,9 @@ function New-PodeEventViewerLoggingMethod { .PARAMETER AsUTC If set, logs the time in UTC instead of local time. +.PARAMETER DefaultTag + The tag to use if none is specified on the log entry. Defaults to '-'. + .EXAMPLE $logMethod = New-PodeSyslogLoggingMethod -Server '192.168.1.100' -Transport 'TCP' -SyslogProtocol 'RFC3164' @@ -488,7 +508,11 @@ function New-PodeSyslogLoggingMethod { [Parameter()] [switch] - $AsUTC + $AsUTC, + + [Parameter()] + [string] + $DefaultTag = '-' ) # Determine the date format based on parameter set @@ -534,6 +558,7 @@ function New-PodeSyslogLoggingMethod { FailureAction = $FailureAction DataFormat = $DataFormat AsUTC = $AsUTC.IsPresent + DefaultTag = $DefaultTag } } } @@ -575,6 +600,9 @@ function New-PodeSyslogLoggingMethod { .PARAMETER AsUTC If set, logs the time in UTC instead of local time. +.PARAMETER DefaultTag + The tag to use if none is specified on the log entry. Defaults to '-'. + .OUTPUTS Hashtable: Returns a hashtable containing the RESTful logging method configuration. @@ -632,7 +660,11 @@ function New-PodeRestfulLoggingMethod { [Parameter()] [switch] - $AsUTC + $AsUTC, + + [Parameter()] + [string] + $DefaultTag = '-' ) # Determine the date format based on parameter set @@ -669,6 +701,7 @@ function New-PodeRestfulLoggingMethod { FailureAction = $FailureAction DataFormat = $DataFormat AsUTC = $AsUTC.IsPresent + DefaultTag = $DefaultTag } } } @@ -1764,9 +1797,12 @@ function Write-PodeLog { [string] $Message, + [Parameter(ParameterSetName = 'ErrorRecord')] + [Parameter(ParameterSetName = 'Message')] + [Parameter(ParameterSetName = 'Exception')] [Parameter()] [string] - $Tag = '-', + $Tag, [Parameter(ParameterSetName = 'InputObject')] [Parameter(ParameterSetName = 'Message')] @@ -1793,8 +1829,9 @@ function Write-PodeLog { # Define the log item based on the selected parameter set. switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'inputobject' { - if (!$Level) { $Level = 'Informational' } - if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } + if (!$Level) { $Level = 'Informational' } # Default to Informational. + if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } # If the level is not configured, use the + $logItem = @{ Name = $Name Item = $InputObject @@ -1803,8 +1840,10 @@ function Write-PodeLog { break } 'message' { - if (!$Level) { $Level = 'Informational' } - if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } + if (!$Level) { $Level = 'Informational' } # Default to Informational. + if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } # If the log level is not configured, return. + if ([string]::IsNullOrEmpty($Tag)) { $Tag = $PodeContext.Server.Logging.Type[$Name].Method.Arguments.DefaultTag } # If the tag is not specified, use the default tag for the log method. + $logItem = @{ Name = $Name Item = @{ @@ -1817,8 +1856,10 @@ function Write-PodeLog { break } 'exception' { - if (!$Level) { $Level = 'Error' } - if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } + if (!$Level) { $Level = 'Error' } # Default to Error. + if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } # If the level is not supported, return. + if ([string]::IsNullOrEmpty($Tag)) { $Tag = $PodeContext.Server.Logging.Type[$Name].Method.Arguments.DefaultTag } # If the tag is not specified, use the default tag for the log method. + $logItem = @{ Name = $Name Item = @{ @@ -1830,8 +1871,10 @@ function Write-PodeLog { break } 'errorrecord' { - if (!$Level) { $Level = 'Error' } - if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } + if (!$Level) { $Level = 'Error' } # Default to Error. + if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } # If the level is not supported, return. + if ([string]::IsNullOrEmpty($Tag)) { $Tag = $PodeContext.Server.Logging.Type[$Name].Method.Arguments.DefaultTag } # If the tag is not specified, use the default tag for the log method. + $logItem = @{ Name = $Name Item = @{ From 83b6f5aaa756b83a6ebae7ef8224344c8afaf4fe Mon Sep 17 00:00:00 2001 From: mdaneri Date: Fri, 1 Nov 2024 10:00:20 -0700 Subject: [PATCH 25/53] improvements --- examples/Logging.ps1 | 5 +- src/Private/Logging.ps1 | 104 +++++++++++++++++++++++++--------------- src/Public/Logging.ps1 | 33 +++---------- 3 files changed, 76 insertions(+), 66 deletions(-) diff --git a/examples/Logging.ps1 b/examples/Logging.ps1 index 9086e83d9..2bfcc96a3 100644 --- a/examples/Logging.ps1 +++ b/examples/Logging.ps1 @@ -96,7 +96,8 @@ Start-PodeServer -browse { New-PodeFileLoggingMethod -Name 'error' -MaxDays 4 -Format RFC5424 -ISO8601 | Enable-PodeErrorLogging -Raw -Levels Error @( - (New-PodeFileLoggingMethod -Name 'default' -MaxDays 4 -Format Simple -ISO8601) + (New-PodeFileLoggingMethod -Name 'default' -MaxDays 4 -Format Simple -ISO8601 -DefaultTag 'filetest') + (New-PodeFileLoggingMethod -Name 'defaultRFC5424' -MaxDays 4 -Format RFC5424 -ISO8601 -DefaultTag 'filetestRFC5424') (New-PodeSyslogLoggingMethod -Server 127.0.0.1 -Transport UDP -AsUTC -ISO8601 -SyslogProtocol RFC3164 -FailureAction Report -DefaultTag 'test') ) | Enable-PodeDefaultLogging -Raw $logging | Add-PodeLogging -Name 'mylog' -Raw:$Raw @@ -106,6 +107,8 @@ Start-PodeServer -browse { Add-PodeRoute -Method Get -Path '/' -ScriptBlock { Write-PodeLog -Name 'mylog' -Message 'My custom log' -Level 'Info' Write-PodeLog -Message 'This is for the deafult log.' + Start-Sleep -Seconds 2 + Write-PodeLog -Message 'An allert with a new tag.' -Tag 'newTag' -Level Alert Write-PodeViewResponse -Path 'simple' -Data @{ 'numbers' = @(1, 2, 3); } } diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index a0b61acd9..488105b31 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -1,6 +1,5 @@ using namespace Pode - <# .SYNOPSIS Defines the method for writing log messages to the terminal. @@ -123,12 +122,12 @@ function Get-PodeLoggingFileMethod { else { if ($RawItem -is [array]) { $tmpStrings = @() - foreach ($item in $RawItem) { + foreach ($ritem in $RawItem) { if ($Options.Format -eq 'Simple') { - $tmpStrings += (ConvertTo-PodeSyslogFormat -RawItem $item -MaxLength $Options.MaxLength -Source $Options.Source -DataFormat $Options.DataFormat -Separator $Options.Separator) + $tmpStrings += (ConvertTo-PodeSyslogFormat -RawItem $ritem -Options $Options) } else { - $outString = ConvertTo-PodeSyslogFormat -RawItem $item -RFC $Options.Format -Source $Options.Source + $tmpStrings += ConvertTo-PodeSyslogFormat -RawItem $ritem -RFC $Options.Format -Options $Options } } @@ -137,10 +136,10 @@ function Get-PodeLoggingFileMethod { } else { if ($Options.Format -eq 'Simple') { - $outString = ConvertTo-PodeSyslogFormat -RawItem $RawItem -MaxLength $Options.MaxLength -Source $Options.Source -DataFormat $Options.DataFormat -Separator $Options.Separator + $outString = ConvertTo-PodeSyslogFormat -RawItem $RawItem -Options $Options } else { - $outString = ConvertTo-PodeSyslogFormat -RawItem $RawItem -RFC $Options.Format -Source $Options.Source + $outString = ConvertTo-PodeSyslogFormat -RawItem $RawItem -RFC $Options.Format -Options $Options } } @@ -168,33 +167,54 @@ function Get-PodeLoggingFileMethod { } } -function ConvertTo-PodeSyslogFormat { - [CmdletBinding(DefaultParameterSetName = 'Custom')] - param( - [hashtable] - $RawItem, +<# +.SYNOPSIS +Converts a log item to a formatted syslog message. - [Parameter(Mandatory = $true, ParameterSetName = 'RFC')] - [ValidateSet('RFC3164', 'RFC5424')] - [string] - $RFC, +.DESCRIPTION +The `ConvertTo-PodeSyslogFormat` function takes a log item in hashtable format and converts it into a syslog message, formatted according to the specified RFC standard (RFC3164, RFC5424, or a custom format). The function generates syslog-compliant messages based on severity and other options provided. - [string] - $Source, +.PARAMETER RawItem +A hashtable representing the log data, including fields like `Message`, `StackTrace`, `Level`, `Date`, and `Tag`. - [Parameter( ParameterSetName = 'Custom')] - [int] - $MaxLength, +.PARAMETER RFC +Specifies the syslog message format. Supported values are `RFC3164`, `RFC5424`, and `None` for a custom format. The default is `None`. - [Parameter( ParameterSetName = 'Custom')] - [string] - $DataFormat, +.PARAMETER Options +Additional options to customize the message format. The hashtable may include: +- `DefaultTag`: Default tag used if `RawItem` does not contain a `Tag`. +- `MaxLength`: Maximum allowed message length. +- `DataFormat`: Custom date format for timestamps. +- `Separator`: Separator used between fields in custom format. - [Parameter( ParameterSetName = 'Custom')] - [string] - $Separator = ' ' +.EXAMPLE +PS> ConvertTo-PodeSyslogFormat -RawItem @{ Message = "System Error"; Level = "error"; Date = (Get-Date); Tag = "App" } -RFC 'RFC3164' -Options @{ DefaultTag = "MyApp"; MaxLength = 1024 } +Converts a log item to an RFC3164-formatted syslog message with a maximum length of 1024 characters and a custom tag. +.EXAMPLE +PS> ConvertTo-PodeSyslogFormat -RawItem @{ Message = "Connection established"; Level = "info"; Date = (Get-Date) } -Options @{ DefaultTag = "Service"; DataFormat = "yyyy-MM-dd HH:mm:ss"; Separator = " | " } + +Creates a custom-formatted log message with a user-defined timestamp format and separator. + +.NOTES +This function sets a severity level based on the `Level` field in `RawItem`. If the `RFC` parameter is set to `None`, the message will follow a custom format using the `Options` hashtable. +#> +function ConvertTo-PodeSyslogFormat { + param( + + [Parameter(Mandatory = $true )] + [hashtable] + $RawItem, + + [Parameter(Mandatory = $false )] + [ValidateSet('RFC3164', 'RFC5424', 'None')] + [string] + $RFC = 'None', + + [Parameter(Mandatory = $true )] + [hashtable] + $Options ) $MaxLength = -1 # Mask values @@ -206,7 +226,6 @@ function ConvertTo-PodeSyslogFormat { $message = ($RawItem.Message | Protect-PodeLogItem) } } -#write-podehost $RawItem -Explode # Map $Level to syslog severity switch ($RawItem.Level) { 'emergency' { $severity = 0; break } @@ -221,6 +240,14 @@ function ConvertTo-PodeSyslogFormat { default { $severity = 6 } # Default to Informational } + # If the tag is not specified, use the default tag for the log method. + if ([string]::IsNullOrEmpty($RawItem.Tag)) { + $tag = $Options.DefaultTag + } + else { + $tag = $RawItem.Tag + } + # Define the facility and severity $facility = 1 # User-level messages $priority = ($facility * 8) + $severity @@ -233,14 +260,14 @@ function ConvertTo-PodeSyslogFormat { $MaxLength = 1024 # Assemble the full syslog formatted message $timestamp = $RawItem.Date.ToString('MMM dd HH:mm:ss') - $fullSyslogMessage = "<$priority>$timestamp $($PodeContext.Server.ComputerName) $($RawItem.Tag): $message" + $fullSyslogMessage = "<$priority>$timestamp $($PodeContext.Server.ComputerName) $($tag): $message" break } 'RFC5424' { $timestamp = $RawItem.Date.ToString('yyyy-MM-ddTHH:mm:ss.ffffffK') # Assemble the full syslog formatted message - $fullSyslogMessage = "<$priority>1 $timestamp $($PodeContext.Server.ComputerName) $($RawItem.Tag) $processId - - $message" + $fullSyslogMessage = "<$priority>1 $timestamp $($PodeContext.Server.ComputerName) $tag $processId - - $message" # Set the max message length per RFC 5424 section 6.1 $MaxLength = 2048 @@ -249,6 +276,9 @@ function ConvertTo-PodeSyslogFormat { } # Simple version default { + $MaxLength = $Options.MaxLength + $DataFormat = $Options.DataFormat + $Separator = $Options.Separator if ($DataFormat) { $timestamp = $RawItem.Date.ToString($DataFormat) } @@ -256,7 +286,7 @@ function ConvertTo-PodeSyslogFormat { $timestamp = $DataFormat } # Assemble the full syslog formatted message - $fullSyslogMessage = "$timestamp$Separator$($RawItem.Level)$Separator$Source$Separator$message" + $fullSyslogMessage = "$timestamp$Separator$($RawItem.Level)$Separator$($tag)$Separator$message" # Set the max message length if ($Options.MaxLength) { $MaxLength = $Options.MaxLength @@ -268,7 +298,6 @@ function ConvertTo-PodeSyslogFormat { if ($MaxLength -gt 0 -and $fullSyslogMessage.Length -gt $MaxLength) { return $fullSyslogMessage.Substring(0, $MaxLength) } - write-podehost $fullSyslogMessage # Return the full syslog formatted message return $fullSyslogMessage } @@ -346,7 +375,7 @@ function Get-PodeLoggingSysLogMethod { } for ($i = 0; $i -lt $RawItem.Length; $i++) { - $fullSyslogMessage = ConvertTo-PodeSyslogFormat -RawItem $RawItem[$i] -RFC $Options.SyslogProtocol -Source $Options.Source + $fullSyslogMessage = ConvertTo-PodeSyslogFormat -RawItem $RawItem[$i] -RFC $Options.SyslogProtocol -Options $Options # Convert the message to a byte array $byteMessage = $($Options.Encoding).GetBytes($fullSyslogMessage) @@ -450,12 +479,11 @@ function Get-PodeLoggingRestfulMethod { $items = $Item | ForEach-Object { @{ - event = ($_ | Protect-PodeLogItem) - host = $PodeContext.Server.ComputerName - source = $Options.source - sourcetype = $RawItem.Tag - time = [math]::Round(($RawItem.Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalSeconds) - fields = @{ + event = ($_ | Protect-PodeLogItem) + host = $PodeContext.Server.ComputerName + source = $RawItem.Tag + time = [math]::Round(($RawItem.Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalSeconds) + fields = @{ severity = $RawItem.Level.ToUpperInvariant() } } diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 21ed3f417..36f12c7f1 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -79,6 +79,7 @@ function New-PodeTerminalLoggingMethod { # Return the logging method configuration return @{ + Type = 'Terminal' Id = $methodId Batch = New-PodeLogBatchInfo Logger = @() @@ -118,9 +119,6 @@ function New-PodeTerminalLoggingMethod { .PARAMETER MaxSize The maximum size of a log file in bytes. Once this size is exceeded, a new log file will be created. Defaults to 0 (no size limit). -.PARAMETER Source - The source of the log entries. Defaults to 'Pode'. - .PARAMETER FailureAction Specifies the action to take if logging fails. Options are: Ignore, Report, Halt (Default: Ignore). @@ -187,10 +185,6 @@ function New-PodeFileLoggingMethod { [int] $MaxSize = 0, - [Parameter()] - [string] - $Source = 'Pode', - [Parameter()] [ValidateSet('Ignore', 'Report', 'Halt')] [string] @@ -250,6 +244,7 @@ function New-PodeFileLoggingMethod { # Return the logging method configuration return @{ + Type = 'File' Id = $methodId Batch = New-PodeLogBatchInfo Logger = @() @@ -267,7 +262,6 @@ function New-PodeFileLoggingMethod { Encoding = $Encoding Format = $Format MaxLength = $MaxLength - Source = $Source Separator = $Separator DefaultTag = $DefaultTag } @@ -379,6 +373,7 @@ function New-PodeEventViewerLoggingMethod { # Return the logging method configuration return @{ + Type = 'EventViewer' Id = $methodId Batch = New-PodeLogBatchInfo Logger = @() @@ -419,9 +414,6 @@ function New-PodeEventViewerLoggingMethod { .PARAMETER Encoding The encoding to use for Syslog messages. Supported values are ASCII, BigEndianUnicode, Default, Unicode, UTF32, UTF7, and UTF8. Defaults to UTF8. -.PARAMETER Source - The source of the log entries. Defaults to 'Pode'. - .PARAMETER SkipCertificateCheck If set, skips certificate validation for TLS connections. @@ -484,10 +476,6 @@ function New-PodeSyslogLoggingMethod { [string] $Encoding = 'UTF8', - [Parameter()] - [string] - $Source = 'Pode', - [Parameter()] [switch] $SkipCertificateCheck, @@ -542,6 +530,7 @@ function New-PodeSyslogLoggingMethod { # Return the logging method configuration return @{ + Type = 'Syslog' Id = $methodId Batch = New-PodeLogBatchInfo Logger = @() @@ -550,7 +539,6 @@ function New-PodeSyslogLoggingMethod { Port = $Port Transport = $Transport Hostname = $Hostname - Source = $Source TlsProtocols = $TlsProtocol SkipCertificateCheck = $SkipCertificateCheck.IsPresent SyslogProtocol = $SyslogProtocol @@ -582,9 +570,6 @@ function New-PodeSyslogLoggingMethod { .PARAMETER Id The optional LogInsight collector ID. -.PARAMETER Source - The source of the log entries. Defaults to 'Pode'. - .PARAMETER SkipCertificateCheck If set, skips certificate validation for HTTPS connections. @@ -636,10 +621,6 @@ function New-PodeRestfulLoggingMethod { [string] $Id, - [Parameter()] - [string] - $Source = 'Pode', - [Parameter()] [switch] $SkipCertificateCheck, @@ -688,13 +669,13 @@ function New-PodeRestfulLoggingMethod { # Return the logging method configuration return @{ + Type = 'Restful' Id = $methodId Batch = New-PodeLogBatchInfo Logger = @() Arguments = @{ Platform = $Platform Hostname = $Hostname - Source = $Source SkipCertificateCheck = $SkipCertificateCheck.IsPresent Token = $Token Id = $Id @@ -838,6 +819,7 @@ function New-PodeCustomLoggingMethod { } return @{ + Type = 'Custom' Id = $methodId Batch = New-PodeLogBatchInfo Logger = @() @@ -1842,7 +1824,6 @@ function Write-PodeLog { 'message' { if (!$Level) { $Level = 'Informational' } # Default to Informational. if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } # If the log level is not configured, return. - if ([string]::IsNullOrEmpty($Tag)) { $Tag = $PodeContext.Server.Logging.Type[$Name].Method.Arguments.DefaultTag } # If the tag is not specified, use the default tag for the log method. $logItem = @{ Name = $Name @@ -1858,7 +1839,6 @@ function Write-PodeLog { 'exception' { if (!$Level) { $Level = 'Error' } # Default to Error. if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } # If the level is not supported, return. - if ([string]::IsNullOrEmpty($Tag)) { $Tag = $PodeContext.Server.Logging.Type[$Name].Method.Arguments.DefaultTag } # If the tag is not specified, use the default tag for the log method. $logItem = @{ Name = $Name @@ -1873,7 +1853,6 @@ function Write-PodeLog { 'errorrecord' { if (!$Level) { $Level = 'Error' } # Default to Error. if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } # If the level is not supported, return. - if ([string]::IsNullOrEmpty($Tag)) { $Tag = $PodeContext.Server.Logging.Type[$Name].Method.Arguments.DefaultTag } # If the tag is not specified, use the default tag for the log method. $logItem = @{ Name = $Name From d4894600a239c684e418edb083f73e8664234a4f Mon Sep 17 00:00:00 2001 From: mdaneri Date: Fri, 1 Nov 2024 10:06:42 -0700 Subject: [PATCH 26/53] add try catch --- src/Private/Logging.ps1 | 157 +++++++++++++++++++--------------------- 1 file changed, 74 insertions(+), 83 deletions(-) diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index 488105b31..a7b759d32 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -685,7 +685,6 @@ function Get-PodeLoggingInbuiltType { $sb.Append((sg $item.Host)).Append(' ').Append((sg $item.User)).Append(' ').Append((sg $item.Request.Method)).Append(' '). $sb.Append((sg $item.Request.Resource)).Append(' ').Append((sg $item.Request.Query)).Append(' ').Append((sg $item.Response.StatusCode)). $sb.Append(' ').Append((sg $item.Response.Size)).Append(' "').Append((sg $item.Request.Agent)).Append('"').ToString() - #return "$($item.Date.ToString('yyyy-MM-dd')) $($item.Date.ToString('HH:mm:ss')) $(sg $item.Host) $(sg $item.User) $(sg $item.Request.Method) $(sg $item.Request.Resource) $(sg $item.Request.Query) $(sg $item.Response.StatusCode) $(sg $item.Response.Size) `"$(sg $item.Request.Agent)`"" } 'common' { return [System.Text.StringBuilder]::new() @@ -693,10 +692,6 @@ function Get-PodeLoggingInbuiltType { Append(([regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2'))).Append('] "'). Append((sg $item.Request.Method)).Append(' ').Append((sg $item.Request.Resource)).Append(' ').Append((sg $item.Request.Protocol)). Append('" ').Append((sg $item.Response.StatusCode)).Append(' ').Append((sg $item.Response.Size)).ToString() - # Build the URL with HTTP method - # $url = "$(sg $item.Request.Method) $(sg $item.Request.Resource) $(sg $item.Request.Protocol)" - # $date = [regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2') - # return "$(sg $item.Host) $(sg $item.RfcUserIdentity) $(sg $item.User) [$date] `"$($url)`" $(sg $item.Response.StatusCode) $(sg $item.Response.Size)" } 'json' { return [System.Text.StringBuilder]::new(). @@ -705,7 +700,6 @@ function Get-PodeLoggingInbuiltType { Append((sg $item.Request.Resource)).Append('","query": "').Append((sg $item.Request.Query)).Append('","status": '). Append((sg $item.Response.StatusCode)).Append(',"response_size": ').Append((sg $item.Response.Size)). Append(',"user_agent": "').Append((sg $item.Request.Agent)).Append('"}').ToString() - # return "{`"time`": `"$($item.Date.ToString('yyyy-MM-ddTHH:mm:ssK'))`",`"remote_ip`": `"$(sg $item.Host)`",`"user`": `"$(sg $item.User)`",`"method`": `"$(sg $item.Request.Method)`",`"uri`": `"$(sg $item.Request.Resource)`",`"query`": `"$(sg $item.Request.Query)`",`"status`": $(sg $item.Response.StatusCode),`"response_size`": $(sg $item.Response.Size),`"user_agent`": `"$(sg $item.Request.Agent)`"}" } # Combined is the default default { @@ -714,12 +708,6 @@ function Get-PodeLoggingInbuiltType { Append('] "').Append((sg $item.Request.Method)).Append(' ').Append((sg $item.Request.Resource)).Append(' '). Append((sg $item.Request.Protocol)).Append('" ').Append((sg $item.Response.StatusCode)).Append(' ').Append((sg $item.Response.Size)). Append(' "').Append((sg $item.Request.Referrer)).Append('" "').Append((sg $item.Request.Agent)).Append('"').ToString() - - # Build the URL with HTTP method - # $url = "$(sg $item.Request.Method) $(sg $item.Request.Resource) $(sg $item.Request.Protocol)" - # $date = [regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2') - # Build and return the request row - # return "$(sg $item.Host) $(sg $item.RfcUserIdentity) $(sg $item.User) [$date] `"$($url)`" $(sg $item.Response.StatusCode) $(sg $item.Response.Size) `"$(sg $item.Request.Referrer)`" `"$(sg $item.Request.Agent)`"" } } return $item @@ -764,7 +752,6 @@ function Get-PodeLoggingInbuiltType { return [System.Text.StringBuilder]::new(). Append('[').Append($item.Date.ToString($options.DataFormat)).Append('] '). Append($item.Level).Append(' ').Append($item.Tag).Append(' ').Append($item.ThreadId).Append(' ').Append($item.Message).ToString() - #return "[$($item.Date.ToString($options.DataFormat))] $($item.Level) $( $item.Tag) $($item.ThreadId) $($item.Message)" } } @@ -780,7 +767,6 @@ function Get-PodeLoggingInbuiltType { return [System.Text.StringBuilder]::new(). Append('[').Append($item.Date.ToString($options.DataFormat)).Append('] '). Append($item.Level).Append(' ').Append($item.Tag).Append(' ').Append($item.ThreadId).Append(' ').Append($item.Message).ToString() - # return "[$($item.Date.ToString($options.DataFormat))] $($item.Level) $( $item.Tag) $($item.ThreadId) $($item.Message)" } } } @@ -1156,89 +1142,94 @@ function Start-PodeLoggerDispatcher { if ( Wait-PodeServerToStart) { try { while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { - # Check if the log queue has reached its limit - if ([Pode.PodeLogger]::Count -ge $PodeContext.Server.Logging.QueueLimit) { - Invoke-PodeHandleFailure -Message "Reached the log Queue Limit of $($PodeContext.Server.Logging.QueueLimit)" -FailureAction $logger.Method.Arguments.FailureAction - } - - # Try to dequeue a log entry from the queue - if ( [Pode.PodeLogger]::TryDequeue([ref]$log)) { - # If the log is null, check batch then sleep and skip - if ($null -eq $log) { - Start-Sleep -Milliseconds 100 - continue + try { + # Check if the log queue has reached its limit + if ([Pode.PodeLogger]::Count -ge $PodeContext.Server.Logging.QueueLimit) { + Invoke-PodeHandleFailure -Message "Reached the log Queue Limit of $($PodeContext.Server.Logging.QueueLimit)" -FailureAction $logger.Method.Arguments.FailureAction } - if ($log.Name -eq 'Listener') { - if ($log.Item -is [System.Exception]) { - Write-PodeErrorLog -Exception $log.Item -Level 'Error' -ThreadId $log.Item.ThreadId + # Try to dequeue a log entry from the queue + if ( [Pode.PodeLogger]::TryDequeue([ref]$log)) { + # If the log is null, check batch then sleep and skip + if ($null -eq $log) { + Start-Sleep -Milliseconds 100 + continue } - else { - Write-PodeLog -Message $log.Item.Message -ThreadId $log.Item.ThreadId -Tag 'Listener' -Level $log.Item.Level + if ($log.Name -eq 'Listener') { - } - continue - } + if ($log.Item -is [System.Exception]) { + Write-PodeErrorLog -Exception $log.Item -Level 'Error' -ThreadId $log.Item.ThreadId + } + else { + Write-PodeLog -Message $log.Item.Message -ThreadId $log.Item.ThreadId -Tag 'Listener' -Level $log.Item.Level - # Run the log item through the appropriate method - $logger = $PodeContext.Server.Logging.Type[$log.Name] - $now = [datetime]::Now - - # Convert the log item into a writable format - $rawItem = $log.Item - $_args = @($log.Item) + @($logger.Arguments) - - $item = @(Invoke-PodeScriptBlock -ScriptBlock $logger.ScriptBlock -Arguments $_args -UsingVariables $logger.UsingVariables -Return -Splat) - - # Check batching - $batch = $logger.Method.Batch - if ($batch.Size -gt 1) { - # Add current item to batch - $batch.Items += $item - $batch.RawItems += $log.Item - $batch.LastUpdate = $now - - # If the current amount of items matches the batch size, write - $item = $null - if ($batch.Items.Length -ge $batch.Size) { - $item = $batch.Items - $rawItem = $batch.RawItems + } + continue } - # If we're writing, reset the items - if ($null -ne $item) { - $batch.Items = @() - $batch.RawItems = @() - } - } + # Run the log item through the appropriate method + $logger = $PodeContext.Server.Logging.Type[$log.Name] + $now = [datetime]::Now + + # Convert the log item into a writable format + $rawItem = $log.Item + $_args = @($log.Item) + @($logger.Arguments) + + $item = @(Invoke-PodeScriptBlock -ScriptBlock $logger.ScriptBlock -Arguments $_args -UsingVariables $logger.UsingVariables -Return -Splat) + + # Check batching + $batch = $logger.Method.Batch + if ($batch.Size -gt 1) { + # Add current item to batch + $batch.Items += $item + $batch.RawItems += $log.Item + $batch.LastUpdate = $now + + # If the current amount of items matches the batch size, write + $item = $null + if ($batch.Items.Length -ge $batch.Size) { + $item = $batch.Items + $rawItem = $batch.RawItems + } - # Send the writable log item off to the log writer - if ($null -ne $item) { - foreach ($method in $logger.Method) { - if ($method.NoRunspace) { - # Legacy for custom methods - # $null = Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Logging.Method[$method.Id].ScriptBlock -Arguments $_args -UsingVariables $method.UsingVariables -Splat - $_args = @(, $item) + @($method.Arguments) + @(, $rawItem) - $null = Invoke-PodeScriptBlock -ScriptBlock $logger.Method.ScriptBlock -Arguments $_args -UsingVariables $logger.Method.UsingVariables -Splat + # If we're writing, reset the items + if ($null -ne $item) { + $batch.Items = @() + $batch.RawItems = @() } - else { - $_args = @{ - Item = $item - Options = $method.Arguments - RawItem = $rawItem + } + + # Send the writable log item off to the log writer + if ($null -ne $item) { + foreach ($method in $logger.Method) { + if ($method.NoRunspace) { + # Legacy for custom methods + # $null = Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Logging.Method[$method.Id].ScriptBlock -Arguments $_args -UsingVariables $method.UsingVariables -Splat + $_args = @(, $item) + @($method.Arguments) + @(, $rawItem) + $null = Invoke-PodeScriptBlock -ScriptBlock $logger.Method.ScriptBlock -Arguments $_args -UsingVariables $logger.Method.UsingVariables -Splat + } + else { + $_args = @{ + Item = $item + Options = $method.Arguments + RawItem = $rawItem + } + $PodeContext.Server.Logging.Method[$method.Id].Queue.Enqueue($_args) } - $PodeContext.Server.Logging.Method[$method.Id].Queue.Enqueue($_args) } } - } - # Small sleep to lower CPU usage - Start-Sleep -Milliseconds 100 + # Small sleep to lower CPU usage + Start-Sleep -Milliseconds 100 + } + else { + # Check the logger batch + Test-PodeLoggerBatch + Start-Sleep -Seconds 5 + } } - else { - # Check the logger batch - Test-PodeLoggerBatch - Start-Sleep -Seconds 5 + catch { + $_ | Write-PodeErrorLog } } } From 4de99c1f1b0821232b40b76da1d38785f70cc33e Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 2 Nov 2024 07:19:41 -0700 Subject: [PATCH 27/53] Add support multiple restful syslog --- docs/Tutorials/Logging/Types/General.md | 10 +- examples/Logging.ps1 | 2 +- src/Pode.psd1 | 20 +- src/Private/Helpers.ps1 | 3 +- src/Private/Logging.ps1 | 97 - src/Public/Core.ps1 | 4 +- src/Public/Logging.ps1 | 877 +-------- src/Public/LoggingMethod.ps1 | 2188 +++++++++++++++++++++++ tests/unit/Helpers.Tests.ps1 | 2 +- 9 files changed, 2231 insertions(+), 972 deletions(-) create mode 100644 src/Public/LoggingMethod.ps1 diff --git a/docs/Tutorials/Logging/Types/General.md b/docs/Tutorials/Logging/Types/General.md index 3ddac7233..1fc2daee2 100644 --- a/docs/Tutorials/Logging/Types/General.md +++ b/docs/Tutorials/Logging/Types/General.md @@ -3,11 +3,11 @@ Pode supports general logging, allowing you to define custom logging methods and log levels. This feature enables you to write logs based on specified methods, ensuring flexibility and control over logging outputs. -To enable general logging, use the `Add-PodeLogging` function. This function takes a hashtable defining the logging method, including a ScriptBlock for log output. You can specify various log levels to be enabled, such as Error, Emergency, Alert, Critical, Warning, Notice, Informational, Info, Verbose, and Debug. +To enable general logging, use the `Add-PodeLoggingMethod` function. This function takes a hashtable defining the logging method, including a ScriptBlock for log output. You can specify various log levels to be enabled, such as Error, Emergency, Alert, Critical, Warning, Notice, Informational, Info, Verbose, and Debug. ## Enabling General Logging -To enable general logging, use the `Add-PodeLogging` function, supplying the necessary parameters: +To enable general logging, use the `Add-PodeLoggingMethod` function, supplying the necessary parameters: - `Method`: The hashtable defining the logging method, including the ScriptBlock for log output. - `Levels`: An array of log levels to be enabled for the logging method (default includes Error, Emergency, Alert, Critical, Warning, Notice, Informational, Info, Verbose, Debug). @@ -18,17 +18,17 @@ To enable general logging, use the `Add-PodeLogging` function, supplying the nec ```powershell $method = New-PodeLoggingMethod -syslog -Server 127.0.0.1 -Transport UDP -$method | Add-PodeLogging -Name "mysyslog" +$method | Add-PodeLoggingMethod -Name "mysyslog" ``` ## Disabling General Logging -To disable a general logging method, use the `Remove-PodeLogging` function with the `Name` parameter: +To disable a general logging method, use the `Remove-PodeLoggingMethod` function with the `Name` parameter: ### Example ```powershell -Remove-PodeLogging -Name 'mysyslog' +Remove-PodeLoggingMethod -Name 'mysyslog' ``` With these functions, Pode ensures robust and customizable logging capabilities, allowing you to manage logs effectively based on your specific requirements. diff --git a/examples/Logging.ps1 b/examples/Logging.ps1 index 2bfcc96a3..23b3b7edf 100644 --- a/examples/Logging.ps1 +++ b/examples/Logging.ps1 @@ -100,7 +100,7 @@ Start-PodeServer -browse { (New-PodeFileLoggingMethod -Name 'defaultRFC5424' -MaxDays 4 -Format RFC5424 -ISO8601 -DefaultTag 'filetestRFC5424') (New-PodeSyslogLoggingMethod -Server 127.0.0.1 -Transport UDP -AsUTC -ISO8601 -SyslogProtocol RFC3164 -FailureAction Report -DefaultTag 'test') ) | Enable-PodeDefaultLogging -Raw - $logging | Add-PodeLogging -Name 'mylog' -Raw:$Raw + $logging | Add-PodeLoggingMethod -Name 'mylog' -Raw:$Raw Write-PodeLog -Name 'mylog' -Message 'just started' -Level 'Info' # GET request for web page on "localhost:8081/" diff --git a/src/Pode.psd1 b/src/Pode.psd1 index ce5bf9ffc..b3eb73129 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -286,17 +286,25 @@ 'New-PodeCustomLoggingMethod', 'New-PodeEventViewerLoggingMethod', 'New-PodeFileLoggingMethod', - 'New-PodeRestfulLoggingMethod', 'New-PodeSyslogLoggingMethod', - 'New-PodeTerminalLoggingMethod' + 'New-PodeTerminalLoggingMethod', + 'New-PodeAwsLoggingMethod', + 'New-PodeAzureLoggingMethod', + 'New-PodeDatadogLoggingMethod', + 'New-PodeElasticsearchLoggingMethod', + 'New-PodeGoogleLoggingMethod', + 'New-PodeGraylogLoggingMethod', + 'New-PodeLogInsightLoggingMethod', + 'New-PodeSplunkLoggingMethod', 'Enable-PodeRequestLogging', 'Enable-PodeErrorLogging', 'Enable-PodeDefaultLogging', - 'Add-PodeLogging', + 'Disable-PodeRequestLogging', 'Disable-PodeErrorLogging', 'Disable-PodeDefaultLogging', - 'Remove-PodeLogging', + 'Add-PodeLoggingMethod', + 'Remove-PodeLoggingMethod', 'Add-PodeLogger', 'Remove-PodeLogger', 'Clear-PodeLogger', @@ -304,8 +312,8 @@ 'Write-PodeLog', 'Protect-PodeLogItem', 'Use-PodeLogging', - 'Enable-PodeLogging', - 'Disable-PodeLogging', + 'Enable-PodeLog', + 'Disable-PodeLog', 'Clear-PodeLogging', 'Get-PodeLoggingLevel', diff --git a/src/Private/Helpers.ps1 b/src/Private/Helpers.ps1 index 80b8213b9..961662bbb 100644 --- a/src/Private/Helpers.ps1 +++ b/src/Private/Helpers.ps1 @@ -615,7 +615,7 @@ function Close-PodeServerInternal { $ShowDoneMessage ) #Disable Logging before closing - Disable-PodeLogging + Disable-PodeLog # ensure the token is cancelled if ($null -ne $PodeContext.Tokens.Cancellation) { @@ -3821,4 +3821,3 @@ function Copy-PodeObjectDeepClone { return [System.Management.Automation.PSSerializer]::Deserialize($xmlSerializer) } } - \ No newline at end of file diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index a7b759d32..190efc0c9 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -440,103 +440,6 @@ function Get-PodeLoggingSysLogMethod { } } -<# -.SYNOPSIS -Defines the method for sending log messages to a Restful API endpoint. - -.DESCRIPTION -This internal function handles the sending of log messages to Restful API endpoints for platforms like Splunk and Log Insight. It formats log messages, manages headers, and includes error handling based on user-defined actions. - -.NOTES -This is an internal function and may change in future releases of Pode. -#> -function Get-PodeLoggingRestfulMethod { - return { - param($MethodId) - - $log = @{ } - while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { - Start-Sleep -Milliseconds 100 - - if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { - if ($null -ne $log) { - $Item = $log.Item - $Options = $log.Options - $RawItem = $log.RawItem - - # Ensure item and rawItem are arrays - $Item = @($Item) - $RawItem = @($RawItem) - - # Determine the platform and send the message - switch ($Options.Platform) { - 'Splunk' { - $url = "$($Options.BaseUrl)/services/collector" - $headers = @{ - 'Authorization' = "Splunk $($Options.Token)" - 'Content-Type' = 'application/json' - } - - $items = $Item | ForEach-Object { - @{ - event = ($_ | Protect-PodeLogItem) - host = $PodeContext.Server.ComputerName - source = $RawItem.Tag - time = [math]::Round(($RawItem.Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalSeconds) - fields = @{ - severity = $RawItem.Level.ToUpperInvariant() - } - } - } - $body = $items | ConvertTo-Json -Compress - - try { - Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -SkipCertificateCheck:$Options.SkipCertificateCheck - } - catch { - Invoke-PodeHandleFailure -Message "Failed to send log to Splunk: $_" -FailureAction $Options.FailureAction - } - break - } - - 'LogInsight' { - $url = "$($Options.BaseUrl)/api/v1/messages/ingest/$($Options.Id)" - $headers = @{ - 'Content-Type' = 'application/json' - } - - $messages = $Item | ForEach-Object { - @{ - text = ($_ | Protect-PodeLogItem) - timestamp = [math]::Round(($RawItem.Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalMilliseconds) - fields = @{ - severity = $RawItem.Level.ToUpperInvariant() - tag = $RawItem.Tag - } - } - } - - $payload = @{ - messages = $messages - } - $body = $payload | ConvertTo-Json -Compress - - try { - Invoke-RestMethod -Uri $url -Method Post -Body $body -Headers $headers -SkipCertificateCheck:$Options.SkipCertificateCheck - } - catch { - Invoke-PodeHandleFailure -Message "Failed to send log to LogInsight: $_" -FailureAction $Options.FailureAction - } - break - } - } - } - } - } - } - -} - <# .SYNOPSIS Defines the method for sending log messages to the Windows Event Viewer. diff --git a/src/Public/Core.ps1 b/src/Public/Core.ps1 index c8921f283..238eb5aeb 100644 --- a/src/Public/Core.ps1 +++ b/src/Public/Core.ps1 @@ -192,9 +192,9 @@ function Start-PodeServer { } if ($PodeContext.Server.Logging.Enabled) { - Enable-PodeLogging + Enable-PodeLog } - + # start the file monitor for interally restarting Start-PodeFileMonitor diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 36f12c7f1..475bb2591 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -1,835 +1,5 @@ -<# -.SYNOPSIS - Creates a new terminal logging method in Pode. - -.DESCRIPTION - This function sets up a logging method that outputs log messages to the terminal using Pode's internal terminal logging logic. It allows specifying a custom date format, or uses the ISO 8601 format if requested. Additionally, it supports logging time in UTC. - -.PARAMETER DataFormat - The custom date format to use for log entries. If not provided, a default format of 'dd/MMM/yyyy:HH:mm:ss zzz' is used. - This parameter is mutually exclusive with the ISO8601 parameter. - -.PARAMETER ISO8601 - If set, the date format will follow ISO 8601 (equivalent to -DataFormat 'yyyy-MM-ddTHH:mm:ssK'). - This parameter is mutually exclusive with the DataFormat parameter. - -.PARAMETER AsUTC - If set, the time will be logged in UTC instead of local time. - -.PARAMETER DefaultTag - The tag to use if none is specified on the log entry. Defaults to '-'. - -.OUTPUTS - Hashtable: Returns a hashtable containing the logging method configuration. - -.EXAMPLE - $logMethod = New-PodeTerminalLoggingMethod -DataFormat 'yyyy/MM/dd HH:mm:ss' - - Creates a terminal logging method using the specified custom date format. - -.EXAMPLE - $logMethod = New-PodeTerminalLoggingMethod -ISO8601 -AsUTC - - Creates a terminal logging method that logs messages using the ISO 8601 date format and logs the time in UTC. -#> -function New-PodeTerminalLoggingMethod { - [CmdletBinding(DefaultParameterSetName = 'DataFormat')] - [OutputType([hashtable])] - param( - [Parameter(ParameterSetName = 'DataFormat')] - [ValidateScript({ - Test-PodeDateFormat $_ - })] - [string] - $DataFormat, - - [Parameter(ParameterSetName = 'ISO8601')] - [switch] - $ISO8601, - - [Parameter()] - [switch] - $AsUTC, - - [Parameter()] - [string] - $DefaultTag = '-' - ) - - # Determine the date format based on parameter set - switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { - 'iso8601' { - # Use ISO8601 format if specified - $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' - } - default { - # Use default format if no DataFormat is provided - if ([string]::IsNullOrEmpty($DataFormat)) { - $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format - } - } - } - - # Terminal logging logic - $methodId = New-PodeGuid - $PodeContext.Server.Logging.Method[$methodId] = @{ - ScriptBlock = (Get-PodeLoggingTerminalMethod) - Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() - } - - # Return the logging method configuration - return @{ - Type = 'Terminal' - Id = $methodId - Batch = New-PodeLogBatchInfo - Logger = @() - Arguments = @{ - DataFormat = $DataFormat - AsUTC = $AsUTC.IsPresent - DefaultTag = $DefaultTag - } - } -} - -<# -.SYNOPSIS - Creates a new file-based logging method in Pode. - -.DESCRIPTION - This function sets up a logging method that outputs log messages to a file. It supports configuring log file paths, names, formats, sizes, and retention policies, along with various log formatting options such as custom date formats or ISO 8601. - -.PARAMETER Path - The file path where the logs will be stored. Defaults to './logs'. - -.PARAMETER Name - The base name for the log files. This parameter is mandatory. - -.PARAMETER Format - The format of the log entries. Supported options are: RFC3164, RFC5424, Simple, and Default (Default: Default). - -.PARAMETER Separator - The character(s) used to separate log fields in each entry. Defaults to a space (' '). - -.PARAMETER MaxLength - The maximum length of log entries. Defaults to -1 (no limit). - -.PARAMETER MaxDays - The maximum number of days to keep log files. Logs older than this will be removed automatically. Defaults to 0 (no automatic removal). - -.PARAMETER MaxSize - The maximum size of a log file in bytes. Once this size is exceeded, a new log file will be created. Defaults to 0 (no size limit). - -.PARAMETER FailureAction - Specifies the action to take if logging fails. Options are: Ignore, Report, Halt (Default: Ignore). - -.PARAMETER DataFormat - The custom date format for log entries. Mutually exclusive with ISO8601. - -.PARAMETER Encoding - The encoding to use for Syslog messages. Supported values are ASCII, BigEndianUnicode, Default, Unicode, UTF32, UTF7, and UTF8. Defaults to UTF8. - -.PARAMETER ISO8601 - If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. - -.PARAMETER AsUTC - If set, logs the time in UTC instead of the local time. - -.PARAMETER DefaultTag - The tag to use if none is specified on the log entry. Defaults to '-'. - -.OUTPUTS - Hashtable: Returns a hashtable containing the logging method configuration. - - -.EXAMPLE - $logMethod = New-PodeFileLoggingMethod -Path './logs' -Name 'requests' - - Creates a new file logging method that stores logs in the './logs' directory with the base name 'requests'. - -.EXAMPLE - $logMethod = New-PodeFileLoggingMethod -Name 'requests' -MaxDays 7 -MaxSize 100MB - - Creates a file logging method that keeps logs for 7 days and creates new files once the log file reaches 100MB in size. -#> -function New-PodeFileLoggingMethod { - [CmdletBinding(DefaultParameterSetName = 'DataFormat')] - [OutputType([hashtable])] - param( - [string] - $Path = './logs', - - [Parameter(Mandatory = $true)] - [string] - $Name, - - [Parameter()] - [ValidateSet('RFC3164', 'RFC5424', 'Simple', 'Default')] - [string] - $Format = 'Default', - - [Parameter()] - [string] - $Separator = ' ', - - [Parameter()] - [int] - $MaxLength = -1, - - [Parameter()] - [ValidateRange(0, [int]::MaxValue)] - [int] - $MaxDays = 0, - - [Parameter()] - [ValidateRange(0, [int]::MaxValue)] - [int] - $MaxSize = 0, - - [Parameter()] - [ValidateSet('Ignore', 'Report', 'Halt')] - [string] - $FailureAction = 'Ignore', - - [Parameter(ParameterSetName = 'DataFormat')] - [ValidateScript({ - Test-PodeDateFormat $_ - })] - [string] - $DataFormat, - - [Parameter()] - [ValidateSet('ASCII', 'BigEndianUnicode', 'Default', 'Unicode', 'UTF32', 'UTF7', 'UTF8')] - [string] - $Encoding = 'UTF8', - - [Parameter(ParameterSetName = 'ISO8601')] - [switch] - $ISO8601, - - [Parameter()] - [switch] - $AsUTC, - - [Parameter()] - [string] - $DefaultTag = '-' - ) - - # Determine the date format based on the parameter set - switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { - 'iso8601' { - $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format - } - default { - if ([string]::IsNullOrEmpty($DataFormat)) { - $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format - } - } - } - - # Resolve the log file path - $Path = (Protect-PodeValue -Value $Path -Default './logs') - $Path = (Get-PodeRelativePath -Path $Path -JoinRoot -Resolve) - if (! (Test-Path -Path $Path -PathType Leaf)) { - $null = New-Item -Path $Path -ItemType Directory -Force - } - # Create a unique ID for this logging method - $methodId = New-PodeGuid - - # Register the logging method in Pode's context - $PodeContext.Server.Logging.Method[$methodId] = @{ - ScriptBlock = (Get-PodeLoggingFileMethod) - Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() - } - - # Return the logging method configuration - return @{ - Type = 'File' - Id = $methodId - Batch = New-PodeLogBatchInfo - Logger = @() - Arguments = @{ - Name = $Name - Path = $Path - MaxDays = $MaxDays - MaxSize = $MaxSize - FileId = 0 - Date = $null - NextClearDown = [datetime]::Now.Date - FailureAction = $FailureAction - DataFormat = $DataFormat - AsUTC = $AsUTC.IsPresent - Encoding = $Encoding - Format = $Format - MaxLength = $MaxLength - Separator = $Separator - DefaultTag = $DefaultTag - } - } -} - -<# -.SYNOPSIS - Creates a new Event Viewer logging method in Pode. - -.DESCRIPTION - This function sets up a logging method that outputs log messages to the Windows Event Viewer. It allows configuring the log name, source, and event ID, along with date formatting options like custom formats or ISO 8601. - -.PARAMETER EventLogName - The name of the event log to write to. Defaults to 'Application'. - -.PARAMETER Source - The source of the log entries. Defaults to 'Pode'. - -.PARAMETER EventID - The ID of the event to log. Defaults to 0. - -.PARAMETER FailureAction - Specifies the action to take if logging fails. Options are: Ignore, Report, Halt (Default: Ignore). - -.PARAMETER DataFormat - The custom date format for log entries. Mutually exclusive with ISO8601. - -.PARAMETER ISO8601 - If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. - -.PARAMETER AsUTC - If set, logs the time in UTC instead of local time. - -.OUTPUTS - Hashtable: Returns a hashtable containing the logging method configuration. - -.EXAMPLE - $logMethod = New-PodeEventViewerLoggingMethod -EventLogName 'Application' -Source 'PodeApp' - - Creates a new Event Viewer logging method that writes to the 'Application' log with the source 'PodeApp'. - -.EXAMPLE - $logMethod = New-PodeEventViewerLoggingMethod -Source 'MyApp' -EventID 1001 -ISO8601 - - Creates a new Event Viewer logging method with ISO 8601 date format, writing to the 'MyApp' source and using event ID 1001. - -#> -function New-PodeEventViewerLoggingMethod { - [CmdletBinding(DefaultParameterSetName = 'DataFormat')] - [OutputType([hashtable])] - param( - [string] - $EventLogName = 'Application', - - [string] - $Source = 'Pode', - - [int] - $EventID = 0, - - [ValidateSet('Ignore', 'Report', 'Halt')] - [string] - $FailureAction = 'Ignore', - - [Parameter(ParameterSetName = 'DataFormat')] - [ValidateScript({ Test-PodeDateFormat $_ })] - [string] - $DataFormat, - - [Parameter(ParameterSetName = 'ISO8601')] - [switch] - $ISO8601, - - [Parameter()] - [switch] - $AsUTC - ) - - # Determine the date format based on parameter set - switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { - 'iso8601' { - $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format - } - default { - if ([string]::IsNullOrEmpty($DataFormat)) { - $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format - } - } - } - - # Check if the platform is Windows - if (!(Test-PodeIsWindows)) { - # Event Viewer logging is only supported on Windows - throw ($PodeLocale.eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage) - } - - # Ensure the event source exists in the Event Log - if (![System.Diagnostics.EventLog]::SourceExists($Source)) { - [System.Diagnostics.EventLog]::CreateEventSource($Source, $EventLogName) - } - - # Create the method ID and configure the logging method - $methodId = New-PodeGuid - $PodeContext.Server.Logging.Method[$methodId] = @{ - ScriptBlock = (Get-PodeLoggingEventViewerMethod) - Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() - } - - # Return the logging method configuration - return @{ - Type = 'EventViewer' - Id = $methodId - Batch = New-PodeLogBatchInfo - Logger = @() - Arguments = @{ - LogName = $EventLogName - Source = $Source - ID = $EventID - FailureAction = $FailureAction - DataFormat = $DataFormat - AsUTC = $AsUTC.IsPresent - Tag = $Source - } - } -} - -<# -.SYNOPSIS - Creates a new Syslog logging method in Pode. - -.DESCRIPTION - This function sets up a logging method that sends log messages to a remote Syslog server. It supports various Syslog protocols (RFC3164, RFC5424), transports (UDP, TCP, TLS), and encoding formats. The function also allows for custom date formatting or ISO 8601 compliance and can skip certificate checks for TLS connections. - -.PARAMETER Server - The Syslog server to send logs to. This parameter is mandatory. - -.PARAMETER Port - The port on the Syslog server to send logs to. Defaults to 514. - -.PARAMETER Transport - The transport protocol to use. Supported values are UDP, TCP, and TLS. Defaults to UDP. - -.PARAMETER TlsProtocol - The TLS protocol version to use if TLS transport is selected. Defaults to TLS 1.3. - -.PARAMETER SyslogProtocol - The Syslog protocol to use for message formatting. Supported values are RFC3164 and RFC5424. Defaults to RFC5424. - -.PARAMETER Encoding - The encoding to use for Syslog messages. Supported values are ASCII, BigEndianUnicode, Default, Unicode, UTF32, UTF7, and UTF8. Defaults to UTF8. - -.PARAMETER SkipCertificateCheck - If set, skips certificate validation for TLS connections. - -.PARAMETER FailureAction - Specifies the action to take if logging fails. Options are: Ignore, Report, Halt (Default: Ignore). - -.PARAMETER DataFormat - The custom date format for log entries. Mutually exclusive with ISO8601. -.PARAMETER ISO8601 - If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. -.PARAMETER AsUTC - If set, logs the time in UTC instead of local time. - -.PARAMETER DefaultTag - The tag to use if none is specified on the log entry. Defaults to '-'. - -.EXAMPLE - $logMethod = New-PodeSyslogLoggingMethod -Server '192.168.1.100' -Transport 'TCP' -SyslogProtocol 'RFC3164' - - Creates a new Syslog logging method that sends logs to the Syslog server at 192.168.1.100 using TCP and RFC3164 format. - -.EXAMPLE - $logMethod = New-PodeSyslogLoggingMethod -Server '192.168.1.100' -SyslogProtocol 'RFC5424' -ISO8601 -AsUTC - - Creates a Syslog logging method that uses RFC5424 format with ISO 8601 date formatting and logs time in UTC. - -.OUTPUTS - Hashtable: Returns a hashtable containing the Syslog logging method configuration. -#> -function New-PodeSyslogLoggingMethod { - [CmdletBinding(DefaultParameterSetName = 'DataFormat')] - [OutputType([hashtable])] - param( - [Parameter(Mandatory = $true)] - [string] - $Server, - - [Parameter()] - [Int16] - $Port = 514, - - [Parameter()] - [ValidateSet('UDP', 'TCP', 'TLS')] - [string] - $Transport = 'UDP', - - [Parameter()] - [System.Security.Authentication.SslProtocols] - $TlsProtocol = [System.Security.Authentication.SslProtocols]::Tls13, - - [Parameter()] - [ValidateSet('RFC3164', 'RFC5424')] - [string] - $SyslogProtocol = 'RFC5424', - - [Parameter()] - [ValidateSet('ASCII', 'BigEndianUnicode', 'Default', 'Unicode', 'UTF32', 'UTF7', 'UTF8')] - [string] - $Encoding = 'UTF8', - - [Parameter()] - [switch] - $SkipCertificateCheck, - - [Parameter()] - [ValidateSet('Ignore', 'Report', 'Halt')] - [string] - $FailureAction = 'Ignore', - - [Parameter(ParameterSetName = 'DataFormat')] - [ValidateScript({ Test-PodeDateFormat $_ })] - [string] - $DataFormat, - - [Parameter(ParameterSetName = 'ISO8601')] - [switch] - $ISO8601, - - [Parameter()] - [switch] - $AsUTC, - - [Parameter()] - [string] - $DefaultTag = '-' - ) - - # Determine the date format based on parameter set - switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { - 'iso8601' { - $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format - } - default { - if ([string]::IsNullOrEmpty($DataFormat)) { - $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format - } - } - } - - # Select encoding based on the provided value - $selectedEncoding = [System.Text.Encoding]::$Encoding - if ($null -eq $selectedEncoding) { - throw ($PodeLocale.invalidEncodingExceptionMessage -f $Encoding) - } - - # Create the method ID and configure the logging method - $methodId = New-PodeGuid - $PodeContext.Server.Logging.Method[$methodId] = @{ - ScriptBlock = (Get-PodeLoggingSysLogMethod) - Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() - } - - # Return the logging method configuration - return @{ - Type = 'Syslog' - Id = $methodId - Batch = New-PodeLogBatchInfo - Logger = @() - Arguments = @{ - Server = $Server - Port = $Port - Transport = $Transport - Hostname = $Hostname - TlsProtocols = $TlsProtocol - SkipCertificateCheck = $SkipCertificateCheck.IsPresent - SyslogProtocol = $SyslogProtocol - Encoding = $selectedEncoding - FailureAction = $FailureAction - DataFormat = $DataFormat - AsUTC = $AsUTC.IsPresent - DefaultTag = $DefaultTag - } - } -} - -<# -.SYNOPSIS - Creates a new RESTful logging method in Pode. - -.DESCRIPTION - This function sets up a logging method that sends log messages to a RESTful endpoint. It supports different platforms like Splunk or LogInsight, as well as options for date formatting, skipping certificate validation, and handling failures. - -.PARAMETER BaseUrl - The base URL of the RESTful logging endpoint. This parameter is mandatory. - -.PARAMETER Platform - The platform for RESTful logging. Supported platforms are: Splunk and LogInsight. Defaults to 'Splunk'. - -.PARAMETER Token - An optional token for authentication with the RESTful logging endpoint, if required by the platform. - -.PARAMETER Id - The optional LogInsight collector ID. - -.PARAMETER SkipCertificateCheck - If set, skips certificate validation for HTTPS connections. - -.PARAMETER FailureAction - Specifies the action to take if logging fails. Options are: Ignore, Report, Halt (Default: Ignore). - -.PARAMETER DataFormat - The custom date format for log entries. Mutually exclusive with ISO8601. - -.PARAMETER ISO8601 - If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. - -.PARAMETER AsUTC - If set, logs the time in UTC instead of local time. - -.PARAMETER DefaultTag - The tag to use if none is specified on the log entry. Defaults to '-'. - -.OUTPUTS - Hashtable: Returns a hashtable containing the RESTful logging method configuration. - -.EXAMPLE - $logMethod = New-PodeRestfulLoggingMethod -BaseUrl 'https://logserver.example.com' -Platform 'Splunk' -Token 'your-token' - - Creates a RESTful logging method that sends logs to a Splunk server using the specified token for authentication. - -.EXAMPLE - $logMethod = New-PodeRestfulLoggingMethod -BaseUrl '/api/logs' -ISO8601 -AsUTC - - Creates a RESTful logging method that sends logs using ISO 8601 date format and logs time in UTC. -#> -function New-PodeRestfulLoggingMethod { - [CmdletBinding(DefaultParameterSetName = 'DataFormat')] - [OutputType([hashtable])] - param( - [Parameter(Mandatory = $true)] - [ValidatePattern('^(https?://|/).+')] - [string] - $BaseUrl, - - [ValidateSet('Splunk', 'LogInsight')] - [string]$Platform = 'Splunk', - - [Parameter()] - [string] - $Token, - - [Parameter()] - [string] - $Id, - - [Parameter()] - [switch] - $SkipCertificateCheck, - - [Parameter()] - [ValidateSet('Ignore', 'Report', 'Halt')] - [string] - $FailureAction = 'Ignore', - - [Parameter(ParameterSetName = 'DataFormat')] - [ValidateScript({ Test-PodeDateFormat $_ })] - [string] - $DataFormat, - - [Parameter(ParameterSetName = 'ISO8601')] - [switch] - $ISO8601, - - [Parameter()] - [switch] - $AsUTC, - - [Parameter()] - [string] - $DefaultTag = '-' - ) - - # Determine the date format based on parameter set - switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { - 'iso8601' { - $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format - } - default { - if ([string]::IsNullOrEmpty($DataFormat)) { - $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format - } - } - } - - # Create the method ID and configure the logging method - $methodId = New-PodeGuid - $PodeContext.Server.Logging.Method[$methodId] = @{ - ScriptBlock = (Get-PodeLoggingRestfulMethod) - Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() - } - - # Return the logging method configuration - return @{ - Type = 'Restful' - Id = $methodId - Batch = New-PodeLogBatchInfo - Logger = @() - Arguments = @{ - Platform = $Platform - Hostname = $Hostname - SkipCertificateCheck = $SkipCertificateCheck.IsPresent - Token = $Token - Id = $Id - FailureAction = $FailureAction - DataFormat = $DataFormat - AsUTC = $AsUTC.IsPresent - DefaultTag = $DefaultTag - } - } -} - -<# -.SYNOPSIS - Creates a new custom logging method in Pode. - -.DESCRIPTION - This function sets up a custom logging method that uses a script block to define the logging logic. It supports the option to run the logging method in a separate runspace and allows for custom options, date formatting, and failure handling. - -.PARAMETER ScriptBlock - A non-empty script block that defines the custom logging logic. This parameter is mandatory. - -.PARAMETER ArgumentList - An array of arguments to pass to the custom script block. - -.PARAMETER CustomOptions - A hashtable of custom options that will be passed to the script block when used inside a runspace. - -.PARAMETER FailureAction - Specifies the action to take if logging fails. Options are: Ignore, Report, Halt (Default: Ignore). - -.PARAMETER DataFormat - The custom date format for log entries. Mutually exclusive with ISO8601. - -.PARAMETER ISO8601 - If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. - -.PARAMETER AsUTC - If set, logs the time in UTC instead of local time. - -.EXAMPLE - $logMethod = New-PodeCustomLoggingMethod -ScriptBlock { param($logItem) Write-Output $logItem } -UseRunspace - - Creates a custom logging method using a script block that writes log items to the output. The method runs in a separate runspace. - -.EXAMPLE - $logMethod = New-PodeCustomLoggingMethod -ScriptBlock { param($logItem) Write-Output $logItem } -DataFormat 'yyyy/MM/dd HH:mm:ss' - - Creates a custom logging method with a custom date format. - -.OUTPUTS - Hashtable: Returns a hashtable containing the custom logging method configuration. -#> -function New-PodeCustomLoggingMethod { - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUSeDeclaredVarsMoreThanAssignments', '')] - [CmdletBinding(DefaultParameterSetName = 'DataFormat')] - [OutputType([hashtable])] - param( - [Parameter(Mandatory = $true)] - [ValidateScript({ - if (Test-PodeIsEmpty $_) { - # A non-empty ScriptBlock is required for the Custom logging output method - throw ($PodeLocale.nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage) - } - return $true - }) - ] - [scriptblock] - $ScriptBlock, - - [Parameter()] - [object[]] - $ArgumentList, - - [Parameter()] - [hashtable] - $CustomOptions = @{}, - - [Parameter()] - [ValidateSet('Ignore', 'Report', 'Halt')] - [string] - $FailureAction = 'Ignore', - - [Parameter(ParameterSetName = 'DataFormat')] - [ValidateScript({ Test-PodeDateFormat $_ })] - [string] - $DataFormat, - - [Parameter(ParameterSetName = 'ISO8601')] - [switch] - $ISO8601, - - [Parameter()] - [switch] - $AsUTC - ) - - # Determine the date format based on the parameter set - switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { - 'iso8601' { - $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' - } - default { - if ([string]::IsNullOrEmpty($DataFormat)) { - $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format - } - } - } - - # Create the script block for the custom logging method running in a separate runspace - $enanchedScriptBlock = { - param($MethodId) - - $log = @{} - while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { - Start-Sleep -Milliseconds 100 - - if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { - if ($null -ne $log) { - $Item = $log.item - $Options = $log.options - $RawItem = $log.rawItem - try { - # Original ScriptBlock Start - <# ScriptBlock #> - # Original ScriptBlock End - } - catch { - Invoke-PodeHandleFailure -Message "Custom Logging $MethodId Error. message: $_" -FailureAction $options.FailureAction - } - } - } - } - } - - $methodId = New-PodeGuid - - # Register the enhanced script block in Pode's logging method - $PodeContext.Server.Logging.Method[$methodId] = @{ - ScriptBlock = [ScriptBlock]::Create($enanchedScriptBlock.ToString().Replace('<# ScriptBlock #>', $ScriptBlock.ToString())) - Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() - } - - return @{ - Type = 'Custom' - Id = $methodId - Batch = New-PodeLogBatchInfo - Logger = @() - Arguments = @{ - FailureAction = $FailureAction - DataFormat = $DataFormat - AsUTC = $AsUTC - } + $CustomOptions - } -} <# .SYNOPSIS @@ -1288,9 +458,9 @@ function Enable-PodeDefaultLogging { .EXAMPLE $method = New-PodeLoggingMethod -syslog -Server 127.0.0.1 -Transport UDP - $method | Add-PodeLogging -Name "mysyslog" + $method | Add-PodeLoggingMethod -Name "mysyslog" #> -function Add-PodeLogging { +function Add-PodeLoggingMethod { [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] @@ -1381,9 +551,9 @@ function Disable-PodeRequestLogging { The name of the logging method to be disable. .EXAMPLE - Remove-PodeLogging -Name 'TestLog' + Remove-PodeLoggingMethod -Name 'TestLog' #> -function Remove-PodeLogging { +function Remove-PodeLoggingMethod { [CmdletBinding()] param( [Parameter(Mandatory = $true)] @@ -1615,12 +785,6 @@ if (!(Test-Path Alias:Clear-PodeLoggers)) { .PARAMETER ErrorRecord The error record to log. This is used when handling errors through PowerShell's error handling mechanism. -.PARAMETER Message - A custom error message to log when exceptions or error records are not available. - -.PARAMETER Category - The category of the custom error message (Default: NotSpecified). - .PARAMETER Level The logging level for the error. Supported levels are: Error, Warning, Informational, Verbose, Debug (Default: Error). @@ -1634,6 +798,9 @@ if (!(Test-Path Alias:Clear-PodeLoggers)) { A string that identifies the source application, service, or process generating the log message. The tag helps distinguish log messages from different sources, making it easier to filter and analyze logs. Default is '-'. +.PARAMETER SuppressDefaultLog + A switch to suppress writing the error to the default log. + .EXAMPLE try { # Some operation @@ -1644,8 +811,6 @@ if (!(Test-Path Alias:Clear-PodeLoggers)) { .EXAMPLE [System.Exception]::new('Custom error message') | Write-PodeErrorLog -CheckInnerException -.EXAMPLE - Write-PodeErrorLog -Message "Custom message" -Category NotSpecified -Level 'Warning' #> function Write-PodeErrorLog { @@ -1657,12 +822,6 @@ function Write-PodeErrorLog { [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'ErrorRecord')] [System.Management.Automation.ErrorRecord] $ErrorRecord, - [Parameter(Mandatory = $true, ParameterSetName = 'Message')] - [string] $Message, - - [Parameter(ParameterSetName = 'Message')] - [System.Management.Automation.ErrorCategory] $Category = [System.Management.Automation.ErrorCategory]::NotSpecified, - [Parameter()] [ValidateNotNullOrEmpty()] [ValidateSet('Error', 'Emergency', 'Alert', 'Critical', 'Warning', 'Notice', 'Informational', 'Info', 'Verbose', 'Debug' )] @@ -1675,18 +834,20 @@ function Write-PodeErrorLog { [int] $ThreadId, [string] - $Tag = '-' + $Tag = '-', + + [Parameter()] + [switch] + $SuppressDefaultLog ) Process { $name = Get-PodeErrorLoggingName Write-PodeLog @PSBoundParameters -name $name -SuppressErrorLog - if ($PodeContext.Server.Logging.Type[$name].DuplicateToDefaultLog) { + if ($PodeContext.Server.Logging.Type[$name].DuplicateToDefaultLog -and ! $SuppressDefaultLog) { Write-PodeLog @PSBoundParameters -name (Get-PodeDefaultLoggingName) -SuppressErrorLog } } - - } @@ -2026,14 +1187,14 @@ function Use-PodeLogging { A switch parameter that, if specified, enables terminal logging for the Pode C# listener. .EXAMPLE - Enable-PodeLogging + Enable-PodeLog This example enables all logging except terminal logging. .EXAMPLE - Enable-PodeLogging -Terminal + Enable-PodeLog -Terminal This example enables all logging including terminal logging for the Pode C# listener. #> -function Enable-PodeLogging { +function Enable-PodeLog { param( [switch] $Terminal @@ -2060,14 +1221,14 @@ function Enable-PodeLogging { A switch parameter that, if specified, keeps terminal logging enabled for the Pode C# listener even when other logging is disabled. .EXAMPLE - Disable-PodeLogging + Disable-PodeLog This example disables all logging including terminal logging. .EXAMPLE - Disable-PodeLogging -KeepTerminal + Disable-PodeLog -KeepTerminal This example disables all logging except terminal logging. #> -function Disable-PodeLogging { +function Disable-PodeLog { param( [switch] $KeepTerminal diff --git a/src/Public/LoggingMethod.ps1 b/src/Public/LoggingMethod.ps1 new file mode 100644 index 000000000..39cc08875 --- /dev/null +++ b/src/Public/LoggingMethod.ps1 @@ -0,0 +1,2188 @@ + +<# +.SYNOPSIS + Creates a new terminal logging method in Pode. + +.DESCRIPTION + This function sets up a logging method that outputs log messages to the terminal using Pode's internal terminal logging logic. It allows specifying a custom date format, or uses the ISO 8601 format if requested. Additionally, it supports logging time in UTC. + +.PARAMETER DataFormat + The custom date format to use for log entries. If not provided, a default format of 'dd/MMM/yyyy:HH:mm:ss zzz' is used. + This parameter is mutually exclusive with the ISO8601 parameter. + +.PARAMETER ISO8601 + If set, the date format will follow ISO 8601 (equivalent to -DataFormat 'yyyy-MM-ddTHH:mm:ssK'). + This parameter is mutually exclusive with the DataFormat parameter. + +.PARAMETER AsUTC + If set, the time will be logged in UTC instead of local time. + +.PARAMETER DefaultTag + The tag to use if none is specified on the log entry. Defaults to '-'. + +.OUTPUTS + Hashtable: Returns a hashtable containing the logging method configuration. + +.EXAMPLE + $logMethod = New-PodeTerminalLoggingMethod -DataFormat 'yyyy/MM/dd HH:mm:ss' + + Creates a terminal logging method using the specified custom date format. + +.EXAMPLE + $logMethod = New-PodeTerminalLoggingMethod -ISO8601 -AsUTC + + Creates a terminal logging method that logs messages using the ISO 8601 date format and logs the time in UTC. +#> +function New-PodeTerminalLoggingMethod { + [CmdletBinding(DefaultParameterSetName = 'DataFormat')] + [OutputType([hashtable])] + param( + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ + Test-PodeDateFormat $_ + })] + [string] + $DataFormat, + + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601, + + [Parameter()] + [switch] + $AsUTC, + + [Parameter()] + [string] + $DefaultTag = '-' + ) + + # Determine the date format based on parameter set + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + # Use ISO8601 format if specified + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' + } + default { + # Use default format if no DataFormat is provided + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } + + # Terminal logging logic + $methodId = New-PodeGuid + $PodeContext.Server.Logging.Method[$methodId] = @{ + ScriptBlock = (Get-PodeLoggingTerminalMethod) + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + } + + # Return the logging method configuration + return @{ + Type = 'Terminal' + Id = $methodId + Batch = New-PodeLogBatchInfo + Logger = @() + Arguments = @{ + DataFormat = $DataFormat + AsUTC = $AsUTC.IsPresent + DefaultTag = $DefaultTag + } + } +} + +<# +.SYNOPSIS + Creates a new file-based logging method in Pode. + +.DESCRIPTION + This function sets up a logging method that outputs log messages to a file. It supports configuring log file paths, names, formats, sizes, and retention policies, along with various log formatting options such as custom date formats or ISO 8601. + +.PARAMETER Path + The file path where the logs will be stored. Defaults to './logs'. + +.PARAMETER Name + The base name for the log files. This parameter is mandatory. + +.PARAMETER Format + The format of the log entries. Supported options are: RFC3164, RFC5424, Simple, and Default (Default: Default). + +.PARAMETER Separator + The character(s) used to separate log fields in each entry. Defaults to a space (' '). + +.PARAMETER MaxLength + The maximum length of log entries. Defaults to -1 (no limit). + +.PARAMETER MaxDays + The maximum number of days to keep log files. Logs older than this will be removed automatically. Defaults to 0 (no automatic removal). + +.PARAMETER MaxSize + The maximum size of a log file in bytes. Once this size is exceeded, a new log file will be created. Defaults to 0 (no size limit). + +.PARAMETER FailureAction + Specifies the action to take if logging fails. Options are: Ignore, Report, Halt (Default: Ignore). + +.PARAMETER DataFormat + The custom date format for log entries. Mutually exclusive with ISO8601. + +.PARAMETER Encoding + The encoding to use for Syslog messages. Supported values are ASCII, BigEndianUnicode, Default, Unicode, UTF32, UTF7, and UTF8. Defaults to UTF8. + +.PARAMETER ISO8601 + If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. + +.PARAMETER AsUTC + If set, logs the time in UTC instead of the local time. + +.PARAMETER DefaultTag + The tag to use if none is specified on the log entry. Defaults to '-'. + +.OUTPUTS + Hashtable: Returns a hashtable containing the logging method configuration. + + +.EXAMPLE + $logMethod = New-PodeFileLoggingMethod -Path './logs' -Name 'requests' + + Creates a new file logging method that stores logs in the './logs' directory with the base name 'requests'. + +.EXAMPLE + $logMethod = New-PodeFileLoggingMethod -Name 'requests' -MaxDays 7 -MaxSize 100MB + + Creates a file logging method that keeps logs for 7 days and creates new files once the log file reaches 100MB in size. +#> +function New-PodeFileLoggingMethod { + [CmdletBinding(DefaultParameterSetName = 'DataFormat')] + [OutputType([hashtable])] + param( + [string] + $Path = './logs', + + [Parameter(Mandatory = $true)] + [string] + $Name, + + [Parameter()] + [ValidateSet('RFC3164', 'RFC5424', 'Simple', 'Default')] + [string] + $Format = 'Default', + + [Parameter()] + [string] + $Separator = ' ', + + [Parameter()] + [int] + $MaxLength = -1, + + [Parameter()] + [ValidateRange(0, [int]::MaxValue)] + [int] + $MaxDays = 0, + + [Parameter()] + [ValidateRange(0, [int]::MaxValue)] + [int] + $MaxSize = 0, + + [Parameter()] + [ValidateSet('Ignore', 'Report', 'Halt')] + [string] + $FailureAction = 'Ignore', + + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ + Test-PodeDateFormat $_ + })] + [string] + $DataFormat, + + [Parameter()] + [ValidateSet('ASCII', 'BigEndianUnicode', 'Default', 'Unicode', 'UTF32', 'UTF7', 'UTF8')] + [string] + $Encoding = 'UTF8', + + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601, + + [Parameter()] + [switch] + $AsUTC, + + [Parameter()] + [string] + $DefaultTag = '-' + ) + + # Determine the date format based on the parameter set + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } + + # Resolve the log file path + $Path = (Protect-PodeValue -Value $Path -Default './logs') + $Path = (Get-PodeRelativePath -Path $Path -JoinRoot -Resolve) + if (! (Test-Path -Path $Path -PathType Leaf)) { + $null = New-Item -Path $Path -ItemType Directory -Force + } + # Create a unique ID for this logging method + $methodId = New-PodeGuid + + # Register the logging method in Pode's context + $PodeContext.Server.Logging.Method[$methodId] = @{ + ScriptBlock = (Get-PodeLoggingFileMethod) + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + } + + # Return the logging method configuration + return @{ + Type = 'File' + Id = $methodId + Batch = New-PodeLogBatchInfo + Logger = @() + Arguments = @{ + Name = $Name + Path = $Path + MaxDays = $MaxDays + MaxSize = $MaxSize + FileId = 0 + Date = $null + NextClearDown = [datetime]::Now.Date + FailureAction = $FailureAction + DataFormat = $DataFormat + AsUTC = $AsUTC.IsPresent + Encoding = $Encoding + Format = $Format + MaxLength = $MaxLength + Separator = $Separator + DefaultTag = $DefaultTag + } + } +} + +<# +.SYNOPSIS + Creates a new Event Viewer logging method in Pode. + +.DESCRIPTION + This function sets up a logging method that outputs log messages to the Windows Event Viewer. It allows configuring the log name, source, and event ID, along with date formatting options like custom formats or ISO 8601. + +.PARAMETER EventLogName + The name of the event log to write to. Defaults to 'Application'. + +.PARAMETER Source + The source of the log entries. Defaults to 'Pode'. + +.PARAMETER EventID + The ID of the event to log. Defaults to 0. + +.PARAMETER FailureAction + Specifies the action to take if logging fails. Options are: Ignore, Report, Halt (Default: Ignore). + +.PARAMETER DataFormat + The custom date format for log entries. Mutually exclusive with ISO8601. + +.PARAMETER ISO8601 + If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. + +.PARAMETER AsUTC + If set, logs the time in UTC instead of local time. + +.OUTPUTS + Hashtable: Returns a hashtable containing the logging method configuration. + +.EXAMPLE + $logMethod = New-PodeEventViewerLoggingMethod -EventLogName 'Application' -Source 'PodeApp' + + Creates a new Event Viewer logging method that writes to the 'Application' log with the source 'PodeApp'. + +.EXAMPLE + $logMethod = New-PodeEventViewerLoggingMethod -Source 'MyApp' -EventID 1001 -ISO8601 + + Creates a new Event Viewer logging method with ISO 8601 date format, writing to the 'MyApp' source and using event ID 1001. + +#> +function New-PodeEventViewerLoggingMethod { + [CmdletBinding(DefaultParameterSetName = 'DataFormat')] + [OutputType([hashtable])] + param( + [string] + $EventLogName = 'Application', + + [string] + $Source = 'Pode', + + [int] + $EventID = 0, + + [ValidateSet('Ignore', 'Report', 'Halt')] + [string] + $FailureAction = 'Ignore', + + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ Test-PodeDateFormat $_ })] + [string] + $DataFormat, + + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601, + + [Parameter()] + [switch] + $AsUTC + ) + + # Determine the date format based on parameter set + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } + + # Check if the platform is Windows + if (!(Test-PodeIsWindows)) { + # Event Viewer logging is only supported on Windows + throw ($PodeLocale.eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage) + } + + # Ensure the event source exists in the Event Log + if (![System.Diagnostics.EventLog]::SourceExists($Source)) { + [System.Diagnostics.EventLog]::CreateEventSource($Source, $EventLogName) + } + + # Create the method ID and configure the logging method + $methodId = New-PodeGuid + $PodeContext.Server.Logging.Method[$methodId] = @{ + ScriptBlock = (Get-PodeLoggingEventViewerMethod) + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + } + + # Return the logging method configuration + return @{ + Type = 'EventViewer' + Id = $methodId + Batch = New-PodeLogBatchInfo + Logger = @() + Arguments = @{ + LogName = $EventLogName + Source = $Source + ID = $EventID + FailureAction = $FailureAction + DataFormat = $DataFormat + AsUTC = $AsUTC.IsPresent + Tag = $Source + } + } +} + + +<# +.SYNOPSIS + Creates a new custom logging method in Pode. + +.DESCRIPTION + This function sets up a custom logging method that uses a script block to define the logging logic. It supports the option to run the logging method in a separate runspace and allows for custom options, date formatting, and failure handling. + +.PARAMETER ScriptBlock + A non-empty script block that defines the custom logging logic. This parameter is mandatory. + +.PARAMETER ArgumentList + An array of arguments to pass to the custom script block. + +.PARAMETER CustomOptions + A hashtable of custom options that will be passed to the script block when used inside a runspace. + +.PARAMETER FailureAction + Specifies the action to take if logging fails. Options are: Ignore, Report, Halt (Default: Ignore). + +.PARAMETER DataFormat + The custom date format for log entries. Mutually exclusive with ISO8601. + +.PARAMETER ISO8601 + If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. + +.PARAMETER AsUTC + If set, logs the time in UTC instead of local time. + +.EXAMPLE + $logMethod = New-PodeCustomLoggingMethod -ScriptBlock { param($logItem) Write-Output $logItem } -UseRunspace + + Creates a custom logging method using a script block that writes log items to the output. The method runs in a separate runspace. + +.EXAMPLE + $logMethod = New-PodeCustomLoggingMethod -ScriptBlock { param($logItem) Write-Output $logItem } -DataFormat 'yyyy/MM/dd HH:mm:ss' + + Creates a custom logging method with a custom date format. + +.OUTPUTS + Hashtable: Returns a hashtable containing the custom logging method configuration. +#> +function New-PodeCustomLoggingMethod { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUSeDeclaredVarsMoreThanAssignments', '')] + [CmdletBinding(DefaultParameterSetName = 'DataFormat')] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $true)] + [ValidateScript({ + if (Test-PodeIsEmpty $_) { + # A non-empty ScriptBlock is required for the Custom logging output method + throw ($PodeLocale.nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage) + } + return $true + }) + ] + [scriptblock] + $ScriptBlock, + + [Parameter()] + [object[]] + $ArgumentList, + + [Parameter()] + [hashtable] + $CustomOptions = @{}, + + [Parameter()] + [ValidateSet('Ignore', 'Report', 'Halt')] + [string] + $FailureAction = 'Ignore', + + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ Test-PodeDateFormat $_ })] + [string] + $DataFormat, + + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601, + + [Parameter()] + [switch] + $AsUTC + ) + + # Determine the date format based on the parameter set + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } + + # Create the script block for the custom logging method running in a separate runspace + $enanchedScriptBlock = { + param($MethodId) + + $log = @{} + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + Start-Sleep -Milliseconds 100 + + if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { + if ($null -ne $log) { + $Item = $log.item + $Options = $log.options + $RawItem = $log.rawItem + try { + # Original ScriptBlock Start + <# ScriptBlock #> + # Original ScriptBlock End + } + catch { + Invoke-PodeHandleFailure -Message "Custom Logging $MethodId Error. message: $_" -FailureAction $options.FailureAction + } + } + } + } + } + + $methodId = New-PodeGuid + + # Register the enhanced script block in Pode's logging method + $PodeContext.Server.Logging.Method[$methodId] = @{ + ScriptBlock = [ScriptBlock]::Create($enanchedScriptBlock.ToString().Replace('<# ScriptBlock #>', $ScriptBlock.ToString())) + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + } + + return @{ + Type = 'Custom' + Id = $methodId + Batch = New-PodeLogBatchInfo + Logger = @() + Arguments = @{ + FailureAction = $FailureAction + DataFormat = $DataFormat + AsUTC = $AsUTC + } + $CustomOptions + } +} + +<# +.SYNOPSIS + Creates a new Syslog logging method in Pode. + +.DESCRIPTION + This function sets up a logging method that sends log messages to a remote Syslog server. It supports various Syslog protocols (RFC3164, RFC5424), transports (UDP, TCP, TLS), and encoding formats. The function also allows for custom date formatting or ISO 8601 compliance and can skip certificate checks for TLS connections. + +.PARAMETER Server + The Syslog server to send logs to. This parameter is mandatory. + +.PARAMETER Port + The port on the Syslog server to send logs to. Defaults to 514. + +.PARAMETER Transport + The transport protocol to use. Supported values are UDP, TCP, and TLS. Defaults to UDP. + +.PARAMETER TlsProtocol + The TLS protocol version to use if TLS transport is selected. Defaults to TLS 1.3. + +.PARAMETER SyslogProtocol + The Syslog protocol to use for message formatting. Supported values are RFC3164 and RFC5424. Defaults to RFC5424. + +.PARAMETER Encoding + The encoding to use for Syslog messages. Supported values are ASCII, BigEndianUnicode, Default, Unicode, UTF32, UTF7, and UTF8. Defaults to UTF8. + +.PARAMETER SkipCertificateCheck + If set, skips certificate validation for TLS connections. + +.PARAMETER FailureAction + Specifies the action to take if logging fails. Options are: Ignore, Report, Halt (Default: Ignore). + +.PARAMETER DataFormat + The custom date format for log entries. Mutually exclusive with ISO8601. + +.PARAMETER ISO8601 + If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. + +.PARAMETER AsUTC + If set, logs the time in UTC instead of local time. + +.PARAMETER DefaultTag + The tag to use if none is specified on the log entry. Defaults to '-'. + +.EXAMPLE + $logMethod = New-PodeSyslogLoggingMethod -Server '192.168.1.100' -Transport 'TCP' -SyslogProtocol 'RFC3164' + + Creates a new Syslog logging method that sends logs to the Syslog server at 192.168.1.100 using TCP and RFC3164 format. + +.EXAMPLE + $logMethod = New-PodeSyslogLoggingMethod -Server '192.168.1.100' -SyslogProtocol 'RFC5424' -ISO8601 -AsUTC + + Creates a Syslog logging method that uses RFC5424 format with ISO 8601 date formatting and logs time in UTC. + +.OUTPUTS + Hashtable: Returns a hashtable containing the Syslog logging method configuration. +#> +function New-PodeSyslogLoggingMethod { + [CmdletBinding(DefaultParameterSetName = 'DataFormat')] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $true)] + [string] + $Server, + + [Parameter()] + [Int16] + $Port = 514, + + [Parameter()] + [ValidateSet('UDP', 'TCP', 'TLS')] + [string] + $Transport = 'UDP', + + [Parameter()] + [System.Security.Authentication.SslProtocols] + $TlsProtocol = [System.Security.Authentication.SslProtocols]::Tls13, + + [Parameter()] + [ValidateSet('RFC3164', 'RFC5424')] + [string] + $SyslogProtocol = 'RFC5424', + + [Parameter()] + [ValidateSet('ASCII', 'BigEndianUnicode', 'Default', 'Unicode', 'UTF32', 'UTF7', 'UTF8')] + [string] + $Encoding = 'UTF8', + + [Parameter()] + [switch] + $SkipCertificateCheck, + + [Parameter()] + [ValidateSet('Ignore', 'Report', 'Halt')] + [string] + $FailureAction = 'Ignore', + + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ Test-PodeDateFormat $_ })] + [string] + $DataFormat, + + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601, + + [Parameter()] + [switch] + $AsUTC, + + [Parameter()] + [string] + $DefaultTag = '-' + ) + + # Determine the date format based on parameter set + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } + + # Select encoding based on the provided value + $selectedEncoding = [System.Text.Encoding]::$Encoding + if ($null -eq $selectedEncoding) { + throw ($PodeLocale.invalidEncodingExceptionMessage -f $Encoding) + } + + # Create the method ID and configure the logging method + $methodId = New-PodeGuid + $PodeContext.Server.Logging.Method[$methodId] = @{ + ScriptBlock = (Get-PodeLoggingSysLogMethod) + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + } + + # Return the logging method configuration + return @{ + Type = 'Syslog' + Id = $methodId + Batch = New-PodeLogBatchInfo + Logger = @() + Arguments = @{ + Server = $Server + Port = $Port + Transport = $Transport + Hostname = $Hostname + TlsProtocols = $TlsProtocol + SkipCertificateCheck = $SkipCertificateCheck.IsPresent + SyslogProtocol = $SyslogProtocol + Encoding = $selectedEncoding + FailureAction = $FailureAction + DataFormat = $DataFormat + AsUTC = $AsUTC.IsPresent + DefaultTag = $DefaultTag + } + } +} + +<# +.SYNOPSIS +Configures logging to AWS CloudWatch Logs. + +.DESCRIPTION +The `New-PodeAwsLoggingMethod` function configures a logging method for AWS CloudWatch Logs. It initializes a logging queue and sends log events to AWS CloudWatch using the specified log group and stream names. + +.PARAMETER BaseUrl +The base URL for the AWS CloudWatch Logs API, typically `https://logs..amazonaws.com`. + +.PARAMETER Region +The AWS region where the CloudWatch Log Group resides, such as `us-east-1`. + +.PARAMETER LogGroupName +The name of the AWS CloudWatch Log Group to send logs to. + +.PARAMETER LogStreamName +The name of the AWS CloudWatch Log Stream within the log group. + +.PARAMETER AuthorizationHeader +The AWS authorization header, generated using AWS Signature Version 4. + +.PARAMETER FailureAction +Specifies the action to take if the logging request fails. Valid values are `Ignore`, `Report`, and `Halt`. The default is `Ignore`. + +.PARAMETER SkipCertificateCheck +If present, skips SSL certificate validation when sending logs. + +.PARAMETER AsUTC +If present, converts timestamps to UTC. + +.PARAMETER DefaultTag +Sets a default tag for log entries. Defaults to `-`. + +.PARAMETER DataFormat +The custom date format for log entries. Mutually exclusive with ISO8601. + +.PARAMETER ISO8601 +If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. + +.EXAMPLE +PS> New-PodeAwsLoggingMethod -BaseUrl 'https://logs.us-east-1.amazonaws.com' -Region 'us-east-1' -LogGroupName 'MyLogGroup' -LogStreamName 'MyLogStream' -AuthorizationHeader 'AWS4-HMAC-SHA256 ...' + +Configures AWS CloudWatch logging with specified log group, log stream, and AWS authorization details. + +.NOTES +This function sends logs to AWS CloudWatch in batches, using a `ConcurrentQueue` to manage queued logs. +#> +function New-PodeAwsLoggingMethod { + [CmdletBinding()] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $true)] + [string] + $BaseUrl, + + [Parameter(Mandatory = $true)] + [string] + $Region, + + [Parameter(Mandatory = $true)] + [string] + $LogGroupName, + + [Parameter(Mandatory = $true)] + [string] + $LogStreamName, + + [Parameter(Mandatory = $true)] + [string] + $AuthorizationHeader, + + [Parameter()] + [ValidateSet('Ignore', 'Report', 'Halt')] + [string] + $FailureAction = 'Ignore', + + [Parameter()] + [switch] + $SkipCertificateCheck, + + [Parameter()] + [switch] + $AsUTC, + + [Parameter()] + [string] + $DefaultTag = '-', + + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ Test-PodeDateFormat $_ })] + [string] + $DataFormat, + + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601 + ) + + # Determine the date format based on parameter set + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } + + # Generate a unique ID for this logging method instance. + $methodId = New-PodeGuid + + # Add the logging method configuration to the PodeContext for tracking and execution. + $PodeContext.Server.Logging.Method[$methodId] = @{ + # Queue to hold log entries until they can be processed. + # Using a concurrent queue ensures thread-safe interactions in the runspace. + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + + # ScriptBlock responsible for processing log entries in a separate runspace. + # This block continuously dequeues and sends log entries to AWS CloudWatch Logs. + ScriptBlock = { + param($MethodId) # Pass the unique method ID to identify this logging configuration. + + # Temporary hashtable to hold a dequeued log entry. + $log = @{ } + + # Loop continuously until a cancellation is requested (graceful shutdown). + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + # Brief pause to reduce CPU usage in the loop. + Start-Sleep -Milliseconds 100 + + # Attempt to dequeue a log entry from the queue. + if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { + # Only process if a valid log entry was dequeued. + if ($null -ne $log) { + # Retrieve log data and configuration options. + $Item = $log.Item + $Options = $log.Options + $RawItem = $log.RawItem + + # Ensure both $Item and $RawItem are treated as arrays to handle multiple log entries. + $Item = @($Item) + $RawItem = @($RawItem) + + # Define the AWS CloudWatch Logs endpoint URL. + $url = "https://logs.$($Options.Region).amazonaws.com" + + # Set up headers with the AWS authorization header and content type. + $headers = @{ + 'X-Amz-Date' = (Get-Date -Format 'yyyyMMddTHHmmssZ') # Current timestamp in AWS-required format + 'Content-Type' = 'application/x-amz-json-1.1' + 'X-Amz-Target' = 'Logs_20140328.PutLogEvents' # AWS target for CloudWatch log ingestion + 'Authorization' = $Options.AuthorizationHeader # AWS Signature v4 for authentication + } + + # Format each log entry for CloudWatch Logs. + $events = $Item | ForEach-Object { + @{ + message = ($_ | Protect-PodeLogItem) # Sanitize log message content + timestamp = [math]::Round(($RawItem.Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalMilliseconds) # Timestamp in milliseconds since epoch + } + } + + # Create the payload body for AWS CloudWatch Logs. + $body = @{ + logGroupName = $Options.LogGroupName # Target log group + logStreamName = $Options.LogStreamName # Target log stream within the group + logEvents = $events + } | ConvertTo-Json -Compress + + # Send the log data to CloudWatch Logs via HTTP POST. + try { + Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -SkipCertificateCheck:$Options.SkipCertificateCheck + } + catch { + # Handle any failures based on the configured FailureAction (e.g., Ignore, Report, Halt). + Invoke-PodeHandleFailure -Message "Failed to send log to AWS CloudWatch Logs: $_" -FailureAction $Options.FailureAction + } + } + } + } + } + + } + + # Return the logging method configuration as a hashtable. + return @{ + Type = 'AWS' # Specifies the type of logging platform. + Id = $methodId # Unique identifier for this logging method. + Batch = New-PodeLogBatchInfo # Contains batch information for Pode logging. + Logger = @() # Initialize an empty logger array if needed for Pode processing. + Arguments = @{ + BaseUrl = $BaseUrl + Region = $Region + LogGroupName = $LogGroupName + LogStreamName = $LogStreamName + AuthorizationHeader = $AuthorizationHeader + FailureAction = $FailureAction + SkipCertificateCheck = $SkipCertificateCheck.IsPresent + AsUTC = $AsUTC.IsPresent + DefaultTag = $DefaultTag + DataFormat = $DataFormat + } + } +} + +<# +.SYNOPSIS +Configures logging to Azure Monitor Logs. + +.DESCRIPTION +The `New-PodeAzureLoggingMethod` function sets up logging for Azure Monitor Logs, allowing log data to be sent to a specified Azure Log Analytics workspace. It uses the shared key authorization method to authenticate with Azure. + +.PARAMETER WorkspaceId +The Azure Log Analytics Workspace ID. + +.PARAMETER AuthorizationHeader +The authorization header for Azure, generated using the Workspace ID and shared key. + +.PARAMETER LogType +The custom log type name in Azure Monitor. Defaults to `CustomLog`. + +.PARAMETER FailureAction +Specifies the action to take if the logging request fails. Valid values are `Ignore`, `Report`, and `Halt`. The default is `Ignore`. + +.PARAMETER SkipCertificateCheck +If present, skips SSL certificate validation when sending logs. + +.PARAMETER AsUTC +If present, converts timestamps to UTC. + +.PARAMETER DefaultTag +Sets a default tag for log entries. Defaults to `-`. + +.PARAMETER DataFormat +The custom date format for log entries. Mutually exclusive with ISO8601. + +.PARAMETER ISO8601 +If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. + +.EXAMPLE +PS> New-PodeAzureLoggingMethod -WorkspaceId '12345' -AuthorizationHeader 'SharedKey 12345:abcdef...' -LogType 'ApplicationLogs' + +Sets up Azure Monitor logging with the specified workspace ID and authorization details. + +.NOTES +This function sends logs to Azure Monitor Logs using the Azure REST API, formatted for ingestion by Azure Log Analytics. +#> +function New-PodeAzureLoggingMethod { + [CmdletBinding()] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $true)] + [string] + $WorkspaceId, + + [Parameter(Mandatory = $true)] + [string] + $AuthorizationHeader, # Azure Shared Key authorization + + [Parameter()] + [string] + $LogType = 'CustomLog', + + [Parameter()] + [ValidateSet('Ignore', 'Report', 'Halt')] + [string] + $FailureAction = 'Ignore', + + [Parameter()] + [switch] + $SkipCertificateCheck, + + [Parameter()] + [switch] + $AsUTC, + + [Parameter()] + [string] + $DefaultTag = '-', + + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ Test-PodeDateFormat $_ })] + [string] + $DataFormat, + + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601 + ) + + # Determine the date format based on parameter set + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } + + # Generate a unique ID for this logging method instance. + $methodId = New-PodeGuid + + # Add the logging method configuration to the PodeContext for tracking and execution. + $PodeContext.Server.Logging.Method[$methodId] = @{ + # Queue to hold log entries until they can be processed. + # Using a concurrent queue ensures thread-safe interactions in the runspace. + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + + # ScriptBlock responsible for processing log entries in a separate runspace. + # This block continuously dequeues and sends log entries to Azure Monitor. + ScriptBlock = { + param($MethodId) # Pass the unique method ID to identify this logging configuration. + + # Temporary hashtable to hold a dequeued log entry. + $log = @{ } + + # Loop continuously until a cancellation is requested (graceful shutdown). + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + # Brief pause to reduce CPU usage in the loop. + Start-Sleep -Milliseconds 100 + + # Attempt to dequeue a log entry from the queue. + if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { + # Only process if a valid log entry was dequeued. + if ($null -ne $log) { + # Retrieve log data and configuration options. + $Item = $log.Item + $Options = $log.Options + $RawItem = $log.RawItem + + # Ensure both $Item and $RawItem are treated as arrays to handle multiple log entries. + $Item = @($Item) + $RawItem = @($RawItem) + + # Define the Azure Monitor HTTP Data Collector API endpoint URL for the specified workspace. + $url = "https://$($Options.WorkspaceId).ods.opinsights.azure.com/api/logs?api-version=2016-04-01" + + # Set up headers, including the authorization header, log type, and time-generated field. + $headers = @{ + 'Authorization' = $Options.AuthorizationHeader # Azure Shared Key + 'Log-Type' = $Options.LogType # Specifies the Log Type name + 'x-ms-date' = (Get-Date -Format 'R') # RFC1123 date format for request header + 'time-generated-field' = 'timestamp' + } + + # Format each log entry for Azure Monitor. + $records = $Item | ForEach-Object { + @{ + message = ($_ | Protect-PodeLogItem) # Sanitize log message content + severity = $RawItem.Level.ToUpperInvariant() # Set log severity level + timestamp = $RawItem.Date.ToString('yyyy-MM-ddTHH:mm:ss.fffZ') # Format timestamp in ISO 8601 + tag = $RawItem.Tag # Include tag if provided + } + } + + # Convert the list of records to JSON format for Azure Monitor ingestion. + $body = $records | ConvertTo-Json -Compress + + # Send the log data to Azure Monitor via HTTP POST. + try { + Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -SkipCertificateCheck:$Options.SkipCertificateCheck + } + catch { + # Handle any failures based on the configured FailureAction (e.g., Ignore, Report, Halt). + Invoke-PodeHandleFailure -Message "Failed to send log to Azure Monitor: $_" -FailureAction $Options.FailureAction + } + } + } + } + } + } + + # Return the logging method configuration as a hashtable. + return @{ + Type = 'Azure' # Specifies the type of logging platform. + Id = $methodId # Unique identifier for this logging method. + Batch = New-PodeLogBatchInfo # Contains batch information for Pode logging. + Logger = @() # Initialize an empty logger array if needed for Pode processing. + Arguments = @{ + WorkspaceId = $WorkspaceId + AuthorizationHeader = $AuthorizationHeader + LogType = $LogType + FailureAction = $FailureAction + SkipCertificateCheck = $SkipCertificateCheck.IsPresent + AsUTC = $AsUTC.IsPresent + DefaultTag = $DefaultTag + DataFormat = $DataFormat + } + } +} + +<# +.SYNOPSIS +Configures logging to Google Cloud Logging. + +.DESCRIPTION +The `New-PodeGoogleLoggingMethod` function sets up logging for Google Cloud Logging, allowing log entries to be sent to Google Cloud using the project ID and access token for authentication. + +.PARAMETER ProjectId +The Google Cloud Project ID. + +.PARAMETER AccessToken +OAuth 2.0 access token for authenticating with Google Cloud. + +.PARAMETER LogName +The name of the log in Google Cloud Logging. Defaults to `default_log`. + +.PARAMETER FailureAction +Specifies the action to take if the logging request fails. Valid values are `Ignore`, `Report`, and `Halt`. The default is `Ignore`. + +.PARAMETER SkipCertificateCheck +If present, skips SSL certificate validation when sending logs. + +.PARAMETER AsUTC +If present, converts timestamps to UTC. + +.PARAMETER DefaultTag +Sets a default tag for log entries. Defaults to `-`. + +.PARAMETER DataFormat +The custom date format for log entries. Mutually exclusive with ISO8601. + +.PARAMETER ISO8601 +If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. + +.EXAMPLE +PS> New-PodeGoogleLoggingMethod -ProjectId 'my-project-id' -AccessToken 'ya29.a0AfH6SM...' -LogName 'ApplicationLogs' + +Sets up Google Cloud Logging with the specified project ID and access token. + +.NOTES +This function sends log entries to Google Cloud Logging using the Google Cloud Logging REST API, allowing for structured logging within a specific project. +#> +function New-PodeGoogleLoggingMethod { + [CmdletBinding()] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $true)] + [string] + $ProjectId, + + [Parameter(Mandatory = $true)] + [string] + $AccessToken, # OAuth 2.0 token + + [Parameter()] + [string] + $LogName = 'default_log', + + [Parameter()] + [ValidateSet('Ignore', 'Report', 'Halt')] + [string] + $FailureAction = 'Ignore', + + [Parameter()] + [switch] + $SkipCertificateCheck, + + [Parameter()] + [switch] + $AsUTC, + + [Parameter()] + [string] + $DefaultTag = '-', + + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ Test-PodeDateFormat $_ })] + [string] + $DataFormat, + + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601 + ) + + # Determine the date format based on parameter set + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } + + # Generate a unique ID for this logging method instance. + $methodId = New-PodeGuid + + # Add the logging method configuration to the PodeContext for tracking and execution. + $PodeContext.Server.Logging.Method[$methodId] = @{ + # Queue to hold log entries until they can be processed. + # Using a concurrent queue ensures thread-safe interactions in the runspace. + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + + # ScriptBlock responsible for processing log entries in a separate runspace. + # This block continuously dequeues and sends log entries to Google Cloud Logging. + ScriptBlock = { + param($MethodId) # Pass the unique method ID to identify this logging configuration. + + # Temporary hashtable to hold a dequeued log entry. + $log = @{ } + + # Loop continuously until a cancellation is requested (graceful shutdown). + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + # Brief pause to reduce CPU usage in the loop. + Start-Sleep -Milliseconds 100 + + # Attempt to dequeue a log entry from the queue. + if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { + # Only process if a valid log entry was dequeued. + if ($null -ne $log) { + # Retrieve log data and configuration options. + $Item = $log.Item + $Options = $log.Options + $RawItem = $log.RawItem + + # Ensure both $Item and $RawItem are treated as arrays to handle multiple log entries. + $Item = @($Item) + $RawItem = @($RawItem) + + # Define the Google Cloud Logging API endpoint URL. + $url = 'https://logging.googleapis.com/v2/entries:write' + + # Set up headers with the authorization token and JSON content type. + $headers = @{ + 'Authorization' = "Bearer $($Options.AccessToken)" # OAuth 2.0 Bearer token + 'Content-Type' = 'application/json' + } + + # Format each log entry for Google Cloud Logging. + $entries = $Item | ForEach-Object { + @{ + textPayload = ($_ | Protect-PodeLogItem) # Sanitize log message content + severity = $RawItem.Level.ToUpperInvariant() # Set log severity level + timestamp = $RawItem.Date.ToString('yyyy-MM-ddTHH:mm:ss.fffZ') # Format timestamp in ISO 8601 + labels = @{ + tag = $RawItem.Tag # Include tag if provided + } + resource = @{ + type = 'global' # Set resource type to global + labels = @{ + project_id = $Options.ProjectId # Add the project ID + } + } + } + } + + # Create the payload body for Google Cloud Logging. + $body = @{ + entries = $entries + logName = "projects/$($Options.ProjectId)/logs/$($Options.LogName)" # Define log name path + } | ConvertTo-Json -Compress + + # Send the log data to Google Cloud Logging via HTTP POST. + try { + Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -SkipCertificateCheck:$Options.SkipCertificateCheck + } + catch { + # Handle any failures based on the configured FailureAction (e.g., Ignore, Report, Halt). + Invoke-PodeHandleFailure -Message "Failed to send log to Google Cloud Logging: $_" -FailureAction $Options.FailureAction + } + } + } + } + } + } + + # Return the logging method configuration as a hashtable. + return @{ + Type = 'Google' # Specifies the type of logging platform. + Id = $methodId # Unique identifier for this logging method. + Batch = New-PodeLogBatchInfo # Contains batch information for Pode logging. + Logger = @() # Initialize an empty logger array if needed for Pode processing. + Arguments = @{ + ProjectId = $ProjectId + AccessToken = $AccessToken + LogName = $LogName + FailureAction = $FailureAction + SkipCertificateCheck = $SkipCertificateCheck.IsPresent + AsUTC = $AsUTC.IsPresent + DefaultTag = $DefaultTag + DataFormat = $DataFormat + } + } +} + +<# +.SYNOPSIS +Configures logging to Datadog Logs. + +.DESCRIPTION +The `New-PodeDatadogLoggingMethod` function sets up logging for Datadog, allowing log entries to be sent to Datadog’s log intake endpoint using the provided API key. + +.PARAMETER ApiKey +The Datadog API key used to authenticate requests. + +.PARAMETER BaseUrl +The Datadog intake URL, typically `https://http-intake.logs.datadoghq.com/v1/input`. + +.PARAMETER FailureAction +Specifies the action to take if the logging request fails. Valid values are `Ignore`, `Report`, and `Halt`. The default is `Ignore`. + +.PARAMETER SkipCertificateCheck +If present, skips SSL certificate validation when sending logs. + +.PARAMETER AsUTC +If present, converts timestamps to UTC. + +.PARAMETER DefaultTag +Sets a default tag for log entries. Defaults to `-`. + +.PARAMETER DataFormat +The custom date format for log entries. Mutually exclusive with ISO8601. + +.PARAMETER ISO8601 +If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. + +.EXAMPLE +PS> New-PodeDatadogLoggingMethod -ApiKey 'my-datadog-api-key' -BaseUrl 'https://http-intake.logs.datadoghq.com/v1/input' + +Configures Datadog logging using the provided API key and URL. + +.NOTES +This function sends logs to Datadog Logs using a REST API call with the API key as authorization. +#> +function New-PodeDatadogLoggingMethod { + [CmdletBinding()] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $true)] + [string] + $ApiKey, + + [Parameter(Mandatory = $true)] + [string] + $BaseUrl, + + [Parameter()] + [ValidateSet('Ignore', 'Report', 'Halt')] + [string] + $FailureAction = 'Ignore', + + [Parameter()] + [switch] + $SkipCertificateCheck, + + [Parameter()] + [switch] + $AsUTC, + + [Parameter()] + [string] + $DefaultTag = '-', + + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ Test-PodeDateFormat $_ })] + [string] + $DataFormat, + + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601 + ) + + # Determine the date format based on parameter set + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } + + # Generate a unique ID for this logging method instance. + $methodId = New-PodeGuid + + # Add the logging method configuration to the PodeContext for tracking and execution. + $PodeContext.Server.Logging.Method[$methodId] = @{ + # Queue to hold log entries until they can be processed. + # Using a concurrent queue ensures thread-safe interactions in the runspace. + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + + # ScriptBlock responsible for processing log entries in a separate runspace. + # This block continuously dequeues and sends log entries to Datadog. + ScriptBlock = { + param($MethodId) # Pass the unique method ID to identify this logging configuration. + + # Temporary hashtable to hold a dequeued log entry. + $log = @{ } + + # Loop continuously until a cancellation is requested (graceful shutdown). + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + # Brief pause to reduce CPU usage in the loop. + Start-Sleep -Milliseconds 100 + + # Attempt to dequeue a log entry from the queue. + if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { + # Only process if a valid log entry was dequeued. + if ($null -ne $log) { + # Retrieve log data and configuration options. + $Item = $log.Item + $Options = $log.Options + $RawItem = $log.RawItem + + # Ensure both $Item and $RawItem are treated as arrays to handle multiple log entries. + $Item = @($Item) + $RawItem = @($RawItem) + + # Construct the Datadog intake URL for log ingestion. + $url = $Options.BaseUrl + + # Set up headers with the Datadog API key and JSON content type. + $headers = @{ + 'DD-API-KEY' = $Options.ApiKey # API key for Datadog + 'Content-Type' = 'application/json' + } + + # Format each log entry for Datadog. + $events = $Item | ForEach-Object { + @{ + message = ($_ | Protect-PodeLogItem) # Sanitize log message content + host = $PodeContext.Server.ComputerName # Add hostname + service = $RawItem.Tag # Use tag as the service if provided + date_happened = [math]::Round(($RawItem.Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalSeconds) # Convert timestamp to seconds since epoch + status = $RawItem.Level.ToUpperInvariant() # Set log severity level + } + } + + # Convert the list of events to JSON format for Datadog ingestion. + $body = $events | ConvertTo-Json -Compress + + # Send the log data to Datadog via HTTP POST. + try { + Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -SkipCertificateCheck:$Options.SkipCertificateCheck + } + catch { + # Handle any failures based on the configured FailureAction (e.g., Ignore, Report, Halt). + Invoke-PodeHandleFailure -Message "Failed to send log to Datadog: $_" -FailureAction $Options.FailureAction + } + } + } + } + } + } + + # Return the logging method configuration as a hashtable. + return @{ + Type = 'Datadog' # Specifies the type of logging platform. + Id = $methodId # Unique identifier for this logging method. + Batch = New-PodeLogBatchInfo # Contains batch information for Pode logging. + Logger = @() # Initialize an empty logger array if needed for Pode processing. + Arguments = @{ + ApiKey = $ApiKey + BaseUrl = $BaseUrl + FailureAction = $FailureAction + SkipCertificateCheck = $SkipCertificateCheck.IsPresent + AsUTC = $AsUTC.IsPresent + DefaultTag = $DefaultTag + DataFormat = $DataFormat + } + } +} + + +<# +.SYNOPSIS +Configures logging to Elasticsearch. + +.DESCRIPTION +The `New-PodeElasticsearchLoggingMethod` function configures logging for Elasticsearch, allowing log entries to be sent as documents to a specified Elasticsearch index. + +.PARAMETER BaseUrl +The base URL for the Elasticsearch API, typically `http://:9200`. + +.PARAMETER IndexName +The name of the Elasticsearch index where log entries will be stored. + +.PARAMETER FailureAction +Specifies the action to take if the logging request fails. Valid values are `Ignore`, `Report`, and `Halt`. The default is `Ignore`. + +.PARAMETER SkipCertificateCheck +If present, skips SSL certificate validation when sending logs. + +.PARAMETER AsUTC +If present, converts timestamps to UTC. + +.PARAMETER DefaultTag +Sets a default tag for log entries. Defaults to `-`. + +.PARAMETER DataFormat +The custom date format for log entries. Mutually exclusive with ISO8601. + +.PARAMETER ISO8601 +If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. + +.EXAMPLE +PS> New-PodeElasticsearchLoggingMethod -BaseUrl 'http://localhost:9200' -IndexName 'application-logs' + +Sets up Elasticsearch logging with the specified base URL and index name. + +.NOTES +This function sends log entries to Elasticsearch by creating documents in the specified index. +#> +function New-PodeElasticsearchLoggingMethod { + [CmdletBinding()] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $true)] + [string] + $BaseUrl, + + [Parameter(Mandatory = $true)] + [string] + $IndexName, + + [Parameter()] + [ValidateSet('Ignore', 'Report', 'Halt')] + [string] + $FailureAction = 'Ignore', + + [Parameter()] + [switch] + $SkipCertificateCheck, + + [Parameter()] + [switch] + $AsUTC, + + [Parameter()] + [string] + $DefaultTag = '-', + + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ Test-PodeDateFormat $_ })] + [string] + $DataFormat, + + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601 + ) + + # Determine the date format based on parameter set + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } + + # Generate a unique ID for this logging method instance. + $methodId = New-PodeGuid + + # Add the logging method configuration to the PodeContext for tracking and execution. + $PodeContext.Server.Logging.Method[$methodId] = @{ + # Queue to hold log entries until they can be processed. + # Using a concurrent queue ensures thread-safe interactions in the runspace. + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + + # ScriptBlock responsible for processing log entries in a separate runspace. + # This block continuously dequeues and sends log entries to Elasticsearch. + ScriptBlock = { + param($MethodId) # Pass the unique method ID to identify this logging configuration. + + # Temporary hashtable to hold a dequeued log entry. + $log = @{ } + + # Loop continuously until a cancellation is requested (graceful shutdown). + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + # Brief pause to reduce CPU usage in the loop. + Start-Sleep -Milliseconds 100 + + # Attempt to dequeue a log entry from the queue. + if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { + # Only process if a valid log entry was dequeued. + if ($null -ne $log) { + # Retrieve log data and configuration options. + $Item = $log.Item + $Options = $log.Options + $RawItem = $log.RawItem + + # Ensure both $Item and $RawItem are treated as arrays to handle multiple log entries. + $Item = @($Item) + $RawItem = @($RawItem) + + # Construct the Elasticsearch URL for document ingestion using the specified index. + $url = "$($Options.BaseUrl)/$($Options.IndexName)/_doc/" + + # Set up headers for JSON content type required by Elasticsearch. + $headers = @{ + 'Content-Type' = 'application/json' + } + + # Format each log entry for Elasticsearch. + $documents = $Item | ForEach-Object { + @{ + message = ($_ | Protect-PodeLogItem) # Sanitize log message content + timestamp = $RawItem.Date.ToString('yyyy-MM-ddTHH:mm:ss.fffZ') # Format timestamp in ISO 8601 + severity = $RawItem.Level.ToUpperInvariant() # Set log severity level + host = $PodeContext.Server.ComputerName # Add hostname + tag = $RawItem.Tag # Include tag if provided + } + } + + # Convert the list of documents to JSON format for Elasticsearch ingestion. + $body = $documents | ConvertTo-Json -Compress + + # Send the log data to Elasticsearch via HTTP POST. + try { + Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -SkipCertificateCheck:$Options.SkipCertificateCheck + } + catch { + # Handle any failures based on the configured FailureAction (e.g., Ignore, Report, Halt). + Invoke-PodeHandleFailure -Message "Failed to send log to Elasticsearch: $_" -FailureAction $Options.FailureAction + } + } + } + } + } + } + + # Return the logging method configuration as a hashtable. + return @{ + Type = 'Elasticsearch' # Specifies the type of logging platform. + Id = $methodId # Unique identifier for this logging method. + Batch = New-PodeLogBatchInfo # Contains batch information for Pode logging. + Logger = @() # Initialize an empty logger array if needed for Pode processing. + Arguments = @{ + BaseUrl = $BaseUrl + IndexName = $IndexName + FailureAction = $FailureAction + SkipCertificateCheck = $SkipCertificateCheck.IsPresent + AsUTC = $AsUTC.IsPresent + DefaultTag = $DefaultTag + DataFormat = $DataFormat + } + } +} + +<# +.SYNOPSIS +Configures logging to Graylog. + +.DESCRIPTION +The `New-PodeGraylogLoggingMethod` function sets up logging for Graylog, sending log entries to the Graylog server using GELF (Graylog Extended Log Format) over HTTP. + +.PARAMETER BaseUrl +The base URL for the Graylog API, typically `http://:12201/gelf`. + +.PARAMETER FailureAction +Specifies the action to take if the logging request fails. Valid values are `Ignore`, `Report`, and `Halt`. The default is `Ignore`. + +.PARAMETER SkipCertificateCheck +If present, skips SSL certificate validation when sending logs. + +.PARAMETER AsUTC +If present, converts timestamps to UTC. + +.PARAMETER DefaultTag +Sets a default tag for log entries. Defaults to `-`. + +.PARAMETER DataFormat +The custom date format for log entries. Mutually exclusive with ISO8601. + +.PARAMETER ISO8601 +If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. + +.EXAMPLE +PS> New-PodeGraylogLoggingMethod -BaseUrl 'http://graylog-server:12201/gelf' + +Configures Graylog logging using the specified base URL. + +.NOTES +This function sends logs to Graylog using GELF, which allows for structured logging. +#> +function New-PodeGraylogLoggingMethod { + [CmdletBinding()] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $true)] + [string] + $BaseUrl, + + [Parameter()] + [ValidateSet('Ignore', 'Report', 'Halt')] + [string] + $FailureAction = 'Ignore', + + [Parameter()] + [switch] + $SkipCertificateCheck, + + [Parameter()] + [switch] + $AsUTC, + + [Parameter()] + [string] + $DefaultTag = '-', + + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ Test-PodeDateFormat $_ })] + [string] + $DataFormat, + + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601 + ) + + # Determine the date format based on parameter set + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } + + # Generate a unique ID for this logging method instance. + $methodId = New-PodeGuid + + # Add the logging method configuration to the PodeContext for tracking and execution. + $PodeContext.Server.Logging.Method[$methodId] = @{ + # Queue to hold log entries until they can be processed. + # Using a concurrent queue ensures thread-safe interactions in the runspace. + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + + # ScriptBlock responsible for processing log entries in a separate runspace. + # This block continuously dequeues and sends log entries to Graylog. + ScriptBlock = { + param($MethodId) # Pass the unique method ID to identify this logging configuration. + + # Temporary hashtable to hold a dequeued log entry. + $log = @{ } + + # Loop continuously until a cancellation is requested (graceful shutdown). + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + # Brief pause to reduce CPU usage in the loop. + Start-Sleep -Milliseconds 100 + + # Attempt to dequeue a log entry from the queue. + if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { + # Only process if a valid log entry was dequeued. + if ($null -ne $log) { + # Retrieve log data and configuration options. + $Item = $log.Item + $Options = $log.Options + $RawItem = $log.RawItem + + # Ensure both $Item and $RawItem are treated as arrays to handle multiple log entries. + $Item = @($Item) + $RawItem = @($RawItem) + + # Construct the Graylog HTTP GELF URL. + $url = $Options.BaseUrl + + # Set up headers for JSON content type required by Graylog. + $headers = @{ + 'Content-Type' = 'application/json' + } + + # Format each log entry for Graylog. + $messages = $Item | ForEach-Object { + @{ + version = '1.1' # GELF version + host = $PodeContext.Server.ComputerName # Add hostname + short_message = ($_ | Protect-PodeLogItem) # Sanitize log message content + timestamp = [math]::Round(($RawItem.Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalSeconds) # Convert timestamp to seconds since epoch + level = $RawItem.Level.ToUpperInvariant() # Set log severity level + _tag = $RawItem.Tag # Include tag if provided + } + } + + # Convert the list of messages to JSON format for Graylog ingestion. + $body = $messages | ConvertTo-Json -Compress + + # Send the log data to Graylog via HTTP POST. + try { + Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -SkipCertificateCheck:$Options.SkipCertificateCheck + } + catch { + # Handle any failures based on the configured FailureAction (e.g., Ignore, Report, Halt). + Invoke-PodeHandleFailure -Message "Failed to send log to Graylog: $_" -FailureAction $Options.FailureAction + } + } + } + } + } + } + + # Return the logging method configuration as a hashtable. + return @{ + Type = 'Graylog' # Specifies the type of logging platform. + Id = $methodId # Unique identifier for this logging method. + Batch = New-PodeLogBatchInfo # Contains batch information for Pode logging. + Logger = @() # Initialize an empty logger array if needed for Pode processing. + Arguments = @{ + BaseUrl = $BaseUrl + FailureAction = $FailureAction + SkipCertificateCheck = $SkipCertificateCheck.IsPresent + AsUTC = $AsUTC.IsPresent + DefaultTag = $DefaultTag + DataFormat = $DataFormat + } + } +} + +<# +.SYNOPSIS +Configures logging to Splunk. + +.DESCRIPTION +The `New-PodeSplunkLoggingMethod` function sets up logging for Splunk, sending log entries to a specified Splunk HTTP Event Collector (HEC) endpoint using a specified token. + +.PARAMETER BaseUrl +The base URL for the Splunk HTTP Event Collector, typically `https://:8088/services/collector`. + +.PARAMETER Token +The Splunk HEC token for authentication. + +.PARAMETER FailureAction +Specifies the action to take if the logging request fails. Valid values are `Ignore`, `Report`, and `Halt`. The default is `Ignore`. + +.PARAMETER SkipCertificateCheck +If present, skips SSL certificate validation when sending logs. + +.PARAMETER AsUTC +If present, converts timestamps to UTC. + +.PARAMETER DefaultTag +Sets a default tag for log entries. Defaults to `-`. + +.PARAMETER DataFormat +The custom date format for log entries. Mutually exclusive with ISO8601. + +.PARAMETER ISO8601 +If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. + +.EXAMPLE +PS> New-PodeSplunkLoggingMethod -BaseUrl 'https://splunk-server:8088/services/collector' -Token 'my-splunk-token' + +Configures Splunk logging with the provided URL and token. + +.NOTES +This function sends logs to Splunk through its HTTP Event Collector (HEC). +#> +function New-PodeSplunkLoggingMethod { + [CmdletBinding()] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $true)] + [string] + $BaseUrl, + + [Parameter(Mandatory = $true)] + [string] + $Token, + + [Parameter()] + [ValidateSet('Ignore', 'Report', 'Halt')] + [string] + $FailureAction = 'Ignore', + + [Parameter()] + [switch] + $SkipCertificateCheck, + + [Parameter()] + [switch] + $AsUTC, + + [Parameter()] + [string] + $DefaultTag = '-', + + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ Test-PodeDateFormat $_ })] + [string] + $DataFormat, + + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601 + ) + + # Determine the date format based on parameter set + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } + + # Generate a unique ID for this logging method instance. + $methodId = New-PodeGuid + + # Add the logging method configuration to the PodeContext for tracking and execution. + $PodeContext.Server.Logging.Method[$methodId] = @{ + # Queue to hold log entries until they can be processed. + # Using a concurrent queue ensures thread-safe interactions in the runspace. + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + + # ScriptBlock responsible for processing log entries in a separate runspace. + # This block continuously dequeues and sends log entries to Splunk. + ScriptBlock = { + param($MethodId) # Pass the unique method ID to identify this logging configuration. + + # Temporary hashtable to hold a dequeued log entry. + $log = @{ } + + # Loop continuously until a cancellation is requested (graceful shutdown). + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + # Brief pause to reduce CPU usage in the loop. + Start-Sleep -Milliseconds 100 + + # Attempt to dequeue a log entry from the queue. + if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { + # Only process if a valid log entry was dequeued. + if ($null -ne $log) { + # Retrieve log data and configuration options. + $Item = $log.Item + $Options = $log.Options + $RawItem = $log.RawItem + + # Ensure both $Item and $RawItem are treated as arrays to handle multiple log entries. + $Item = @($Item) + $RawItem = @($RawItem) + + # Construct the Splunk HEC URL. + $url = $Options.BaseUrl + + # Set up headers for Splunk HEC authentication and content type. + $headers = @{ + 'Authorization' = "Splunk $($Options.Token)" # HEC token for Splunk + 'Content-Type' = 'application/json' + } + + # Format each log entry for Splunk. + $events = $Item | ForEach-Object { + @{ + event = ($_ | Protect-PodeLogItem) # Sanitize log message content + host = $PodeContext.Server.ComputerName # Add hostname + source = $RawItem.Tag # Use tag as the source if provided + time = [math]::Round(($RawItem.Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalSeconds) # Convert timestamp to seconds since epoch + fields = @{ + severity = $RawItem.Level.ToUpperInvariant() # Set log severity level + } + } + } + + # Convert the list of events to JSON format for Splunk ingestion. + $body = $events | ConvertTo-Json -Compress + + # Send the log data to Splunk via HTTP POST. + try { + Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -SkipCertificateCheck:$Options.SkipCertificateCheck + } + catch { + # Handle any failures based on the configured FailureAction (e.g., Ignore, Report, Halt). + Invoke-PodeHandleFailure -Message "Failed to send log to Splunk: $_" -FailureAction $Options.FailureAction + } + } + } + } + } + } + + # Return the logging method configuration as a hashtable. + return @{ + Type = 'Splunk' # Specifies the type of logging platform. + Id = $methodId # Unique identifier for this logging method. + Batch = New-PodeLogBatchInfo # Contains batch information for Pode logging. + Logger = @() # Initialize an empty logger array if needed for Pode processing. + Arguments = @{ + BaseUrl = $BaseUrl + Token = $Token + FailureAction = $FailureAction + SkipCertificateCheck = $SkipCertificateCheck.IsPresent + AsUTC = $AsUTC.IsPresent + DefaultTag = $DefaultTag + DataFormat = $DataFormat + } + } +} + +<# +.SYNOPSIS +Configures logging to VMware Log Insight. + +.DESCRIPTION +The `New-PodeLogInsightLoggingMethod` function sets up logging for VMware Log Insight, allowing log entries to be sent to the Log Insight API endpoint. + +.PARAMETER BaseUrl +The base URL for the VMware Log Insight ingestion API, typically `https:///api/v1/messages/ingest/`. + +.PARAMETER Id +The ingestion ID for VMware Log Insight, used to target a specific log stream. + +.PARAMETER FailureAction +Specifies the action to take if the logging request fails. Valid values are `Ignore`, `Report`, and `Halt`. The default is `Ignore`. + +.PARAMETER SkipCertificateCheck +If present, skips SSL certificate validation when sending logs. + +.PARAMETER AsUTC +If present, converts timestamps to UTC. + +.PARAMETER DefaultTag +Sets a default tag for log entries. Defaults to `-`. + +.PARAMETER DataFormat +The custom date format for log entries. Mutually exclusive with ISO8601. + +.PARAMETER ISO8601 +If set, uses the ISO 8601 date format for log entries. Mutually exclusive with DataFormat. + +.EXAMPLE +PS> New-PodeLogInsightLoggingMethod -BaseUrl 'https://loginsight-server/api/v1/messages/ingest/' -Id 'my-log-id' + +Configures Log Insight logging using the provided URL and ID. + +.NOTES +This function sends logs to VMware Log Insight through its ingestion API. +#> +function New-PodeLogInsightLoggingMethod { + [CmdletBinding()] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $true)] + [string] + $BaseUrl, + + [Parameter(Mandatory = $true)] + [string] + $Id, + + [Parameter()] + [ValidateSet('Ignore', 'Report', 'Halt')] + [string] + $FailureAction = 'Ignore', + + [Parameter()] + [switch] + $SkipCertificateCheck, + + [Parameter()] + [switch] + $AsUTC, + + [Parameter()] + [string] + $DefaultTag = '-', + + [Parameter(ParameterSetName = 'DataFormat')] + [ValidateScript({ Test-PodeDateFormat $_ })] + [string] + $DataFormat, + + [Parameter(ParameterSetName = 'ISO8601')] + [switch] + $ISO8601 + ) + + # Determine the date format based on parameter set + switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { + 'iso8601' { + $DataFormat = 'yyyy-MM-ddTHH:mm:ssK' # ISO8601 format + } + default { + if ([string]::IsNullOrEmpty($DataFormat)) { + $DataFormat = 'dd/MMM/yyyy:HH:mm:ss zzz' # Default format + } + } + } + + # Generate a unique method ID for this logging method instance + $methodId = New-PodeGuid + + # Add the logging method configuration to the PodeContext for use in logging + $PodeContext.Server.Logging.Method[$methodId] = @{ + # Queue to hold log entries until they can be processed. + # Using a concurrent queue ensures thread-safe interactions in the runspace. + Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() + + # ScriptBlock responsible for processing log entries in a separate runspace. + # This block continuously dequeues and sends log entries to Splunk. + ScriptBlock = { + param($MethodId) + + # Temporary hashtable to store dequeued log information + $log = @{ } + + # Run while cancellation has not been requested + while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + Start-Sleep -Milliseconds 100 # Sleep briefly to avoid constant polling + + # Try to dequeue a log entry from the method's queue + if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { + if ($null -ne $log) { + # Extract log data and configuration options + $Item = $log.Item + $Options = $log.Options + $RawItem = $log.RawItem + + # Ensure both $Item and $RawItem are arrays to handle multiple log entries + $Item = @($Item) + $RawItem = @($RawItem) + + # Build the target URL for the Log Insight API endpoint + $url = "$($Options.BaseUrl)/$($Options.Id)" + $headers = @{ + 'Content-Type' = 'application/json' + } + + # Process each log entry and format as required by Log Insight + $messages = $Item | ForEach-Object { + @{ + text = ($_ | Protect-PodeLogItem) # Sanitize the log message + timestamp = [math]::Round(($RawItem.Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalMilliseconds) # Convert date to milliseconds since epoch + fields = @{ + severity = $RawItem.Level.ToUpperInvariant() # Add severity level + tag = $RawItem.Tag # Add a tag if provided + } + } + } + + # Prepare the payload with the formatted messages + $payload = @{ + messages = $messages + } + + # Convert the payload to JSON format + $body = $payload | ConvertTo-Json -Compress + + try { + # Send the log data to VMware Log Insight via HTTP POST + Invoke-RestMethod -Uri $url -Method Post -Body $body -Headers $headers -SkipCertificateCheck:$Options.SkipCertificateCheck + } + catch { + # Handle any failures based on the configured FailureAction + Invoke-PodeHandleFailure -Message "Failed to send log to Log Insight: $_" -FailureAction $Options.FailureAction + } + } + } + } + } + } + + # Return the logging method configuration to the caller + return @{ + Type = 'LogInsight' + Id = $methodId + Batch = New-PodeLogBatchInfo # Contains batch information if needed + Logger = @() + Arguments = @{ + BaseUrl = $BaseUrl + Id = $Id + FailureAction = $FailureAction + SkipCertificateCheck = $SkipCertificateCheck.IsPresent + AsUTC = $AsUTC.IsPresent + DefaultTag = $DefaultTag + DataFormat = $DataFormat + } + } +} diff --git a/tests/unit/Helpers.Tests.ps1 b/tests/unit/Helpers.Tests.ps1 index 084d98efb..03bc3eb28 100644 --- a/tests/unit/Helpers.Tests.ps1 +++ b/tests/unit/Helpers.Tests.ps1 @@ -1143,7 +1143,7 @@ Describe 'Close-PodeServerInternal' { Mock Close-PodeDisposable { } Mock Remove-PodePSDrive { } Mock Write-Host { } - Mock Disable-PodeLogging { } + Mock Disable-PodeLog { } } It 'Closes out pode, but with no done flag' { From 7ffef304496d22381eda6189df1e7517f70cea81 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 2 Nov 2024 07:25:47 -0700 Subject: [PATCH 28/53] rename WriteError to LogMessagen and WriteException to LogException --- src/Listener/PodeContext.cs | 30 +++++++++++++++--------------- src/Listener/PodeFileWatcher.cs | 2 +- src/Listener/PodeHelpers.cs | 2 +- src/Listener/PodeListener.cs | 16 ++++++++-------- src/Listener/PodeLogger.cs | 6 +++--- src/Listener/PodeReceiver.cs | 8 ++++---- src/Listener/PodeRequest.cs | 18 +++++++++--------- src/Listener/PodeResponse.cs | 16 ++++++++-------- src/Listener/PodeSignalRequest.cs | 4 ++-- src/Listener/PodeSmtpRequest.cs | 2 +- src/Listener/PodeSocket.cs | 28 ++++++++++++++-------------- src/Listener/PodeTcpRequest.cs | 2 +- src/Listener/PodeWatcher.cs | 8 ++++---- src/Listener/PodeWebSocket.cs | 8 ++++---- 14 files changed, 75 insertions(+), 75 deletions(-) diff --git a/src/Listener/PodeContext.cs b/src/Listener/PodeContext.cs index 14c22a654..5ffc77470 100644 --- a/src/Listener/PodeContext.cs +++ b/src/Listener/PodeContext.cs @@ -130,15 +130,15 @@ private void TimeoutCallback(object state) { try { - PodeLogger.WriteErrorMessage("TimeoutCallback triggered", Listener, PodeLoggingLevel.Debug, this); + PodeLogger.LogMessage("TimeoutCallback triggered", Listener, PodeLoggingLevel.Debug, this); if (Response.SseEnabled || Request.IsWebSocket) { - PodeLogger.WriteErrorMessage("Timeout ignored due to SSE/WebSocket", Listener, PodeLoggingLevel.Debug, this); + PodeLogger.LogMessage("Timeout ignored due to SSE/WebSocket", Listener, PodeLoggingLevel.Debug, this); return; } - PodeLogger.WriteErrorMessage($"Request timeout reached: {Listener.RequestTimeout} seconds", Listener, PodeLoggingLevel.Warning, this); + PodeLogger.LogMessage($"Request timeout reached: {Listener.RequestTimeout} seconds", Listener, PodeLoggingLevel.Warning, this); ContextTimeoutToken.Cancel(); State = PodeContextState.Timeout; @@ -148,11 +148,11 @@ private void TimeoutCallback(object state) Request.Error.Data.Add("PodeStatusCode", 408); Dispose(); - PodeLogger.WriteErrorMessage($"Request timeout reached: Dispose", Listener, PodeLoggingLevel.Debug, this); + PodeLogger.LogMessage($"Request timeout reached: Dispose", Listener, PodeLoggingLevel.Debug, this); } catch (Exception ex) { - PodeLogger.WriteErrorMessage($"Exception in TimeoutCallback: {ex}", Listener, PodeLoggingLevel.Error); + PodeLogger.LogMessage($"Exception in TimeoutCallback: {ex}", Listener, PodeLoggingLevel.Error); } } @@ -201,7 +201,7 @@ private async Task NewRequest() } catch (Exception ex) { - PodeLogger.WriteException(ex, Listener, PodeLoggingLevel.Debug); + PodeLogger.LogException(ex, Listener, PodeLoggingLevel.Debug); State = Request.InputStream == default(Stream) ? PodeContextState.Error : PodeContextState.SslError; @@ -296,14 +296,14 @@ public async Task Receive() State = PodeContextState.Receiving; try { - PodeLogger.WriteErrorMessage($"Receiving request", Listener, PodeLoggingLevel.Verbose, this); + PodeLogger.LogMessage($"Receiving request", Listener, PodeLoggingLevel.Verbose, this); var close = await Request.Receive(ContextTimeoutToken.Token).ConfigureAwait(false); SetContextType(); await EndReceive(close).ConfigureAwait(false); } catch (OperationCanceledException ex) when (ContextTimeoutToken.IsCancellationRequested) { - PodeLogger.WriteErrorMessage("Request timed out during receive operation", Listener, PodeLoggingLevel.Warning, this); + PodeLogger.LogMessage("Request timed out during receive operation", Listener, PodeLoggingLevel.Warning, this); State = PodeContextState.Timeout; // Explicitly set the state to Timeout var timeoutException = new HttpRequestException("Request timed out", ex); timeoutException.Data.Add("PodeStatusCode", 408); @@ -312,7 +312,7 @@ public async Task Receive() } catch (Exception ex) { - PodeLogger.WriteException(ex, Listener, PodeLoggingLevel.Debug); + PodeLogger.LogException(ex, Listener, PodeLoggingLevel.Debug); State = PodeContextState.Error; await PodeSocket.HandleContext(this).ConfigureAwait(false); } @@ -342,7 +342,7 @@ public void StartReceive() NewResponse(); State = PodeContextState.Receiving; PodeSocket.StartReceive(this); - PodeLogger.WriteErrorMessage($"Socket listening", Listener, PodeLoggingLevel.Verbose, this); + PodeLogger.LogMessage($"Socket listening", Listener, PodeLoggingLevel.Verbose, this); } /// @@ -353,7 +353,7 @@ public void StartReceive() /// Thrown if the request cannot be upgraded to a WebSocket. public async Task UpgradeWebSocket(string clientId = null) { - PodeLogger.WriteErrorMessage($"Upgrading Websocket", Listener, PodeLoggingLevel.Verbose, this); + PodeLogger.LogMessage($"Upgrading Websocket", Listener, PodeLoggingLevel.Verbose, this); if (!IsWebSocket) { @@ -395,7 +395,7 @@ public async Task UpgradeWebSocket(string clientId = null) var signal = new PodeSignal(this, HttpRequest.Url.AbsolutePath, clientId); Request = new PodeSignalRequest(HttpRequest, signal); Listener.AddSignal(SignalRequest.Signal); - PodeLogger.WriteErrorMessage($"Websocket upgraded", Listener, PodeLoggingLevel.Verbose, this); + PodeLogger.LogMessage($"Websocket upgraded", Listener, PodeLoggingLevel.Verbose, this); } /// @@ -415,7 +415,7 @@ public void Dispose(bool force) { lock (_lockable) { - PodeLogger.WriteErrorMessage($"Disposing Context", Listener, PodeLoggingLevel.Verbose, this); + PodeLogger.LogMessage($"Disposing Context", Listener, PodeLoggingLevel.Verbose, this); Listener.RemoveProcessingContext(this); if (IsClosed) @@ -485,14 +485,14 @@ public void Dispose(bool force) } catch (Exception ex) { - PodeLogger.WriteException(ex, Listener, PodeLoggingLevel.Error); + PodeLogger.LogException(ex, Listener, PodeLoggingLevel.Error); } finally { // Handle re-receiving or socket cleanup. if ((_awaitingBody || (IsKeepAlive && !IsErrored && !IsTimeout && !Response.SseEnabled)) && !force) { - PodeLogger.WriteErrorMessage($"Re-receiving Request", Listener, PodeLoggingLevel.Verbose, this); + PodeLogger.LogMessage($"Re-receiving Request", Listener, PodeLoggingLevel.Verbose, this); StartReceive(); } else diff --git a/src/Listener/PodeFileWatcher.cs b/src/Listener/PodeFileWatcher.cs index 090a68d08..932e143c5 100644 --- a/src/Listener/PodeFileWatcher.cs +++ b/src/Listener/PodeFileWatcher.cs @@ -87,7 +87,7 @@ private void FileEventHandler(object _, FileSystemEventArgs e) private void FileErrorEventHandler(object _, FileWatcherErrorEventArgs e) { - PodeLogger.WriteException(e.Error, Watcher); + PodeLogger.LogException(e.Error, Watcher); } } } \ No newline at end of file diff --git a/src/Listener/PodeHelpers.cs b/src/Listener/PodeHelpers.cs index 72e8b006b..465e28f60 100644 --- a/src/Listener/PodeHelpers.cs +++ b/src/Listener/PodeHelpers.cs @@ -57,7 +57,7 @@ public static bool IsNetFramework return true; } - PodeLogger.WriteException(ex, connector, level); + PodeLogger.LogException(ex, connector, level); return false; }); } diff --git a/src/Listener/PodeListener.cs b/src/Listener/PodeListener.cs index 5a77a3e07..18db3894b 100644 --- a/src/Listener/PodeListener.cs +++ b/src/Listener/PodeListener.cs @@ -265,37 +265,37 @@ public override void Start() protected override void Close() { // shutdown the sockets - PodeLogger.WriteErrorMessage($"Closing sockets", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closing sockets", this, PodeLoggingLevel.Verbose); for (var i = Sockets.Count - 1; i >= 0; i--) { Sockets[i].Dispose(); } Sockets.Clear(); - PodeLogger.WriteErrorMessage($"Closed sockets", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closed sockets", this, PodeLoggingLevel.Verbose); // close existing contexts - PodeLogger.WriteErrorMessage($"Closing contexts", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closing contexts", this, PodeLoggingLevel.Verbose); foreach (var _context in Contexts.ToArray()) { _context.Dispose(true); } Contexts.Clear(); - PodeLogger.WriteErrorMessage($"Closed contexts", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closed contexts", this, PodeLoggingLevel.Verbose); // close connected signals - PodeLogger.WriteErrorMessage($"Closing signals", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closing signals", this, PodeLoggingLevel.Verbose); foreach (var _signal in Signals.Values.ToArray()) { _signal.Dispose(); } Signals.Clear(); - PodeLogger.WriteErrorMessage($"Closed signals", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closed signals", this, PodeLoggingLevel.Verbose); // close connected server events - PodeLogger.WriteErrorMessage($"Closing server events", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closing server events", this, PodeLoggingLevel.Verbose); foreach (var _sseName in ServerEvents.Values.ToArray()) { foreach (var _sse in _sseName.Values.ToArray()) @@ -307,7 +307,7 @@ protected override void Close() } ServerEvents.Clear(); - PodeLogger.WriteErrorMessage($"Closed server events", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closed server events", this, PodeLoggingLevel.Verbose); } } } \ No newline at end of file diff --git a/src/Listener/PodeLogger.cs b/src/Listener/PodeLogger.cs index 9002139f7..a614210b4 100644 --- a/src/Listener/PodeLogger.cs +++ b/src/Listener/PodeLogger.cs @@ -83,7 +83,7 @@ public static void Clear() } } // Method to log an exception - public static void WriteException(Exception ex, PodeConnector connector = default(PodeConnector), PodeLoggingLevel level = PodeLoggingLevel.Error) + public static void LogException(Exception ex, PodeConnector connector = default(PodeConnector), PodeLoggingLevel level = PodeLoggingLevel.Error) { if (ex == default(Exception)) { @@ -122,8 +122,8 @@ public static void Clear() } } - // Method to log an error message - public static void WriteErrorMessage(string message, PodeConnector connector = default(PodeConnector), PodeLoggingLevel level = PodeLoggingLevel.Error, PodeContext context = default(PodeContext)) + // Method to log a message + public static void LogMessage(string message, PodeConnector connector = default(PodeConnector), PodeLoggingLevel level = PodeLoggingLevel.Error, PodeContext context = default(PodeContext)) { // Do nothing if the message is empty or whitespace if (string.IsNullOrWhiteSpace(message)) diff --git a/src/Listener/PodeReceiver.cs b/src/Listener/PodeReceiver.cs index 267fa897e..95372e640 100644 --- a/src/Listener/PodeReceiver.cs +++ b/src/Listener/PodeReceiver.cs @@ -93,7 +93,7 @@ public void RemoveProcessingWebSocketRequest(PodeWebSocketRequest request) protected override void Close() { // disconnect websockets - PodeLogger.WriteErrorMessage($"Closing client web sockets", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closing client web sockets", this, PodeLoggingLevel.Verbose); foreach (var _webSocket in WebSockets.Values.ToArray()) { @@ -101,10 +101,10 @@ protected override void Close() } WebSockets.Clear(); - PodeLogger.WriteErrorMessage($"Closed client web sockets", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closed client web sockets", this, PodeLoggingLevel.Verbose); // close existing websocket requests - PodeLogger.WriteErrorMessage($"Closing client web sockets requests", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closing client web sockets requests", this, PodeLoggingLevel.Verbose); foreach (var _req in Requests.ToArray()) { @@ -112,7 +112,7 @@ protected override void Close() } Requests.Clear(); - PodeLogger.WriteErrorMessage($"Closed client web requests", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closed client web requests", this, PodeLoggingLevel.Verbose); } } } \ No newline at end of file diff --git a/src/Listener/PodeRequest.cs b/src/Listener/PodeRequest.cs index a1391f827..f0f4a7012 100644 --- a/src/Listener/PodeRequest.cs +++ b/src/Listener/PodeRequest.cs @@ -146,12 +146,12 @@ public async Task UpgradeToSSL(CancellationToken cancellationToken) InputStream = ssl; SslUpgraded = true; } - catch (OperationCanceledException ex) { PodeLogger.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose); } - catch (IOException ex) { PodeLogger.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose); } - catch (ObjectDisposedException ex) { PodeLogger.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose); } + catch (OperationCanceledException ex) { PodeLogger.LogException(ex, Context.Listener, PodeLoggingLevel.Verbose); } + catch (IOException ex) { PodeLogger.LogException(ex, Context.Listener, PodeLoggingLevel.Verbose); } + catch (ObjectDisposedException ex) { PodeLogger.LogException(ex, Context.Listener, PodeLoggingLevel.Verbose); } catch (Exception ex) { - PodeLogger.WriteException(ex, Context.Listener, PodeLoggingLevel.Error); + PodeLogger.LogException(ex, Context.Listener, PodeLoggingLevel.Error); Error = new HttpRequestException(ex.Message, ex); Error.Data.Add("PodeStatusCode", 502); } @@ -238,20 +238,20 @@ public async Task Receive(CancellationToken cancellationToken) } catch (OperationCanceledException ex) { - PodeLogger.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose); + PodeLogger.LogException(ex, Context.Listener, PodeLoggingLevel.Verbose); } catch (IOException ex) { - PodeLogger.WriteException(ex, Context.Listener, PodeLoggingLevel.Verbose); + PodeLogger.LogException(ex, Context.Listener, PodeLoggingLevel.Verbose); } catch (HttpRequestException httpex) { - PodeLogger.WriteException(httpex, Context.Listener, PodeLoggingLevel.Error); + PodeLogger.LogException(httpex, Context.Listener, PodeLoggingLevel.Error); Error = httpex; } catch (Exception ex) { - PodeLogger.WriteException(ex, Context.Listener, PodeLoggingLevel.Error); + PodeLogger.LogException(ex, Context.Listener, PodeLoggingLevel.Error); Error = new HttpRequestException(ex.Message, ex); Error.Data.Add("PodeStatusCode", 400); } @@ -405,7 +405,7 @@ public virtual void Dispose() } PartialDispose(); - PodeLogger.WriteErrorMessage($"Request disposed", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.LogMessage($"Request disposed", Context.Listener, PodeLoggingLevel.Verbose, Context); } } } diff --git a/src/Listener/PodeResponse.cs b/src/Listener/PodeResponse.cs index 7ef174d65..aabdf9a7a 100644 --- a/src/Listener/PodeResponse.cs +++ b/src/Listener/PodeResponse.cs @@ -92,13 +92,13 @@ public async Task Send() return; } - PodeLogger.WriteErrorMessage($"Sending response", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.LogMessage($"Sending response", Context.Listener, PodeLoggingLevel.Verbose, Context); try { await SendHeaders(Context.IsTimeout).ConfigureAwait(false); await SendBody(Context.IsTimeout).ConfigureAwait(false); - PodeLogger.WriteErrorMessage($"Response sent", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.LogMessage($"Response sent", Context.Listener, PodeLoggingLevel.Verbose, Context); } catch (OperationCanceledException) { } catch (IOException) { } @@ -108,7 +108,7 @@ public async Task Send() } catch (Exception ex) { - PodeLogger.WriteException(ex, Context.Listener); + PodeLogger.LogException(ex, Context.Listener); throw; } finally @@ -124,13 +124,13 @@ public async Task SendTimeout() return; } - PodeLogger.WriteErrorMessage($"Sending response timed-out", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.LogMessage($"Sending response timed-out", Context.Listener, PodeLoggingLevel.Verbose, Context); StatusCode = 408; try { await SendHeaders(true).ConfigureAwait(false); - PodeLogger.WriteErrorMessage($"Response timed-out sent", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.LogMessage($"Response timed-out sent", Context.Listener, PodeLoggingLevel.Verbose, Context); } catch (OperationCanceledException) { } catch (IOException) { } @@ -140,7 +140,7 @@ public async Task SendTimeout() } catch (Exception ex) { - PodeLogger.WriteException(ex, Context.Listener); + PodeLogger.LogException(ex, Context.Listener); throw; } finally @@ -366,7 +366,7 @@ public async Task Write(byte[] buffer, bool flush = false) } catch (Exception ex) { - PodeLogger.WriteException(ex, Context.Listener); + PodeLogger.LogException(ex, Context.Listener); throw; } } @@ -462,7 +462,7 @@ public void Dispose() OutputStream = default; } - PodeLogger.WriteErrorMessage($"Response disposed", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.LogMessage($"Response disposed", Context.Listener, PodeLoggingLevel.Verbose, Context); } } } \ No newline at end of file diff --git a/src/Listener/PodeSignalRequest.cs b/src/Listener/PodeSignalRequest.cs index 63fb619a7..e0c79170b 100644 --- a/src/Listener/PodeSignalRequest.cs +++ b/src/Listener/PodeSignalRequest.cs @@ -129,11 +129,11 @@ protected override async Task Parse(byte[] bytes, CancellationToken cancel public override void Dispose() { - // Check if already disposed to avoid repeated cleanup + // Check if already disposed to avoid repeated cleanup if (!IsDisposed) { // Log and send the close frame - PodeLogger.WriteErrorMessage($"Closing Websocket", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.LogMessage($"Closing Websocket", Context.Listener, PodeLoggingLevel.Verbose, Context); _ = Context.Response.WriteFrame(string.Empty, PodeWsOpCode.Close); } diff --git a/src/Listener/PodeSmtpRequest.cs b/src/Listener/PodeSmtpRequest.cs index ecb125d80..0432d2b29 100644 --- a/src/Listener/PodeSmtpRequest.cs +++ b/src/Listener/PodeSmtpRequest.cs @@ -265,7 +265,7 @@ protected override async Task Parse(byte[] bytes, CancellationToken cancel public void Reset() { - PodeLogger.WriteErrorMessage($"Request reset", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.LogMessage($"Request reset", Context.Listener, PodeLoggingLevel.Verbose, Context); _canProcess = false; Headers = new Hashtable(StringComparer.InvariantCultureIgnoreCase); diff --git a/src/Listener/PodeSocket.cs b/src/Listener/PodeSocket.cs index 37d49a4c6..00cf26373 100644 --- a/src/Listener/PodeSocket.cs +++ b/src/Listener/PodeSocket.cs @@ -180,7 +180,7 @@ private async Task StartReceive(Socket acceptedSocket) // Create the context for the connection. var context = new PodeContext(acceptedSocket, this, Listener); - PodeLogger.WriteErrorMessage($"Opening Receive", Listener, PodeLoggingLevel.Verbose, context); + PodeLogger.LogMessage($"Opening Receive", Listener, PodeLoggingLevel.Verbose, context); // Initialize the context. await context.Initialise().ConfigureAwait(false); @@ -200,15 +200,15 @@ private async Task StartReceive(Socket acceptedSocket) /// The context to start receiving for. public void StartReceive(PodeContext context) { - PodeLogger.WriteErrorMessage($"Starting Receive", Listener, PodeLoggingLevel.Verbose, context); + PodeLogger.LogMessage($"Starting Receive", Listener, PodeLoggingLevel.Verbose, context); try { // Run the receive operation asynchronously in a new task. _ = Task.Run(async () => await context.Receive().ConfigureAwait(false), Listener.CancellationToken); } - catch (OperationCanceledException ex) { PodeLogger.WriteException(ex, Listener, PodeLoggingLevel.Verbose); } // Handle cancellation. - catch (IOException ex) { PodeLogger.WriteException(ex, Listener, PodeLoggingLevel.Verbose); } // Handle I/O exceptions. + catch (OperationCanceledException ex) { PodeLogger.LogException(ex, Listener, PodeLoggingLevel.Verbose); } // Handle cancellation. + catch (IOException ex) { PodeLogger.LogException(ex, Listener, PodeLoggingLevel.Verbose); } // Handle I/O exceptions. catch (AggregateException aex) { // Handle aggregated exceptions. @@ -218,7 +218,7 @@ public void StartReceive(PodeContext context) catch (Exception ex) { // Handle any other exceptions. - PodeLogger.WriteException(ex, Listener); + PodeLogger.LogException(ex, Listener); context.Socket.Close(); } } @@ -242,7 +242,7 @@ private void ProcessAccept(SocketAsyncEventArgs args) { if (error != SocketError.Success) { - PodeLogger.WriteErrorMessage($"Closing accepting socket: {error}", Listener, PodeLoggingLevel.Debug); + PodeLogger.LogMessage($"Closing accepting socket: {error}", Listener, PodeLoggingLevel.Debug); } // Close socket if it was accepted but there's an error. @@ -258,15 +258,15 @@ private void ProcessAccept(SocketAsyncEventArgs args) { _ = Task.Run(async () => await StartReceive(accepted), Listener.CancellationToken).ConfigureAwait(false); } - catch (OperationCanceledException ex) { PodeLogger.WriteException(ex, Listener, PodeLoggingLevel.Verbose); } - catch (IOException ex) { PodeLogger.WriteException(ex, Listener, PodeLoggingLevel.Verbose); } + catch (OperationCanceledException ex) { PodeLogger.LogException(ex, Listener, PodeLoggingLevel.Verbose); } + catch (IOException ex) { PodeLogger.LogException(ex, Listener, PodeLoggingLevel.Verbose); } catch (AggregateException aex) { PodeHelpers.HandleAggregateException(aex, Listener, PodeLoggingLevel.Error, true); } catch (Exception ex) { - PodeLogger.WriteException(ex, Listener); + PodeLogger.LogException(ex, Listener); } } @@ -293,7 +293,7 @@ public async Task HandleContext(PodeContext context) if (!(context.Request.Error is HttpRequestException httpRequestException) || ((int)httpRequestException.Data["PodeStatusCode"] != 408)) { - PodeLogger.WriteException(context.Request.Error, Listener); + PodeLogger.LogException(context.Request.Error, Listener); } context.Dispose(true); process = false; @@ -334,13 +334,13 @@ public async Task HandleContext(PodeContext context) { if (context.IsWebSocket) { - PodeLogger.WriteErrorMessage($"Received client signal", Listener, PodeLoggingLevel.Verbose, context); + PodeLogger.LogMessage($"Received client signal", Listener, PodeLoggingLevel.Verbose, context); Listener.AddClientSignal(context.SignalRequest.NewClientSignal()); context.Dispose(); } else { - PodeLogger.WriteErrorMessage($"Received request", Listener, PodeLoggingLevel.Verbose, context); + PodeLogger.LogMessage($"Received request", Listener, PodeLoggingLevel.Verbose, context); Listener.AddContext(context); } } @@ -348,7 +348,7 @@ public async Task HandleContext(PodeContext context) catch (Exception ex) { // Log any exceptions that occur while handling the context. - PodeLogger.WriteException(ex, Listener); + PodeLogger.LogException(ex, Listener); } } @@ -452,7 +452,7 @@ public void Dispose() } catch (Exception ex) { - PodeLogger.WriteException(ex, Listener); + PodeLogger.LogException(ex, Listener); } } finally diff --git a/src/Listener/PodeTcpRequest.cs b/src/Listener/PodeTcpRequest.cs index 90e517dd9..bc059d99a 100644 --- a/src/Listener/PodeTcpRequest.cs +++ b/src/Listener/PodeTcpRequest.cs @@ -66,7 +66,7 @@ protected override Task Parse(byte[] bytes, CancellationToken cancellation public void Reset() { - PodeLogger.WriteErrorMessage($"Request reset", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.LogMessage($"Request reset", Context.Listener, PodeLoggingLevel.Verbose, Context); _body = string.Empty; RawBody = default; } diff --git a/src/Listener/PodeWatcher.cs b/src/Listener/PodeWatcher.cs index 9dbfc63f4..2307ba638 100644 --- a/src/Listener/PodeWatcher.cs +++ b/src/Listener/PodeWatcher.cs @@ -52,7 +52,7 @@ public override void Start() protected override void Close() { // dispose watchers - PodeLogger.WriteErrorMessage($"Closing file watchers", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closing file watchers", this, PodeLoggingLevel.Verbose); foreach (var _watcher in FileWatchers.ToArray()) { @@ -60,10 +60,10 @@ protected override void Close() } FileWatchers.Clear(); - PodeLogger.WriteErrorMessage($"Closed file watchers", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closed file watchers", this, PodeLoggingLevel.Verbose); // dispose existing file events - PodeLogger.WriteErrorMessage($"Closing file events", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closing file events", this, PodeLoggingLevel.Verbose); foreach (var _evt in FileEvents.ToArray()) { @@ -71,7 +71,7 @@ protected override void Close() } FileEvents.Clear(); - PodeLogger.WriteErrorMessage($"Closed file events", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closed file events", this, PodeLoggingLevel.Verbose); } } } \ No newline at end of file diff --git a/src/Listener/PodeWebSocket.cs b/src/Listener/PodeWebSocket.cs index 22efd94a6..71f91ba99 100644 --- a/src/Listener/PodeWebSocket.cs +++ b/src/Listener/PodeWebSocket.cs @@ -105,7 +105,7 @@ public async Task Receive() catch (IOException) { } catch (WebSocketException ex) { - PodeLogger.WriteException(ex, Receiver, PodeLoggingLevel.Debug); + PodeLogger.LogException(ex, Receiver, PodeLoggingLevel.Debug); Dispose(); } finally @@ -139,7 +139,7 @@ public async Task Disconnect(PodeWebSocketCloseFrom closeFrom) if (IsConnected) { - PodeLogger.WriteErrorMessage($"Closing client web socket: {Name}", Receiver, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closing client web socket: {Name}", Receiver, PodeLoggingLevel.Verbose); // only close output in client closing if (closeFrom == PodeWebSocketCloseFrom.Client) @@ -153,12 +153,12 @@ public async Task Disconnect(PodeWebSocketCloseFrom closeFrom) await WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false); } - PodeLogger.WriteErrorMessage($"Closed client web socket: {Name}", Receiver, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closed client web socket: {Name}", Receiver, PodeLoggingLevel.Verbose); } WebSocket.Dispose(); WebSocket = default(ClientWebSocket); - PodeLogger.WriteErrorMessage($"Disconnected client web socket: {Name}", Receiver, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Disconnected client web socket: {Name}", Receiver, PodeLoggingLevel.Verbose); } public void Dispose() From 4877db0a6ef55ce7aede6e20227ae460544e222a Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 2 Nov 2024 07:57:17 -0700 Subject: [PATCH 29/53] moving some log formatting to C# --- src/Listener/PodeFormat.cs | 112 +++++++++++++++++++++++++++++++++++++ src/Listener/PodeLogger.cs | 8 +++ src/Private/Logging.ps1 | 66 ++++------------------ src/Public/Logging.ps1 | 4 +- 4 files changed, 133 insertions(+), 57 deletions(-) create mode 100644 src/Listener/PodeFormat.cs diff --git a/src/Listener/PodeFormat.cs b/src/Listener/PodeFormat.cs new file mode 100644 index 000000000..2f6b16b4e --- /dev/null +++ b/src/Listener/PodeFormat.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Concurrent; +using System.Collections; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace Pode +{ + public static class PodeFormat + { + // Helper function to sanitize and return a default value if the input is null or whitespace + private static string Sanitize(object value) + { + return value == null || string.IsNullOrWhiteSpace(value.ToString()) ? "-" : value.ToString(); + } + + public static object ErrorsLog(Hashtable item, Hashtable options) + { + // Do nothing if the error level isn't present in the options' Levels array + if (options.ContainsKey("Levels") && !((IList)options["Levels"]).Contains(item["Level"])) + { + return null; + } + + // Just return the item if Raw is set + if (options.ContainsKey("Raw") && (bool)options["Raw"]) + { + return item; + } + + // Optimized concatenation using StringBuilder + StringBuilder sb = new StringBuilder(); + return sb.Append("Date: ").Append(((DateTime)item["Date"]).ToString(options["DataFormat"].ToString())).Append(" Level: ").Append(item["Level"]) + .Append(" ThreadId: ").Append(item["ThreadId"]).Append(" Server: ").Append(item["Server"]).Append(" Category: ") + .Append(item["Category"]).Append(" Message: ").Append(item["Message"]).Append(" StackTrace: ").Append(item["StackTrace"]) + .ToString(); + } + + public static object RequestLog(Hashtable item, Hashtable options) + { + // Just return the item if Raw is set + if (options.ContainsKey("Raw") && (bool)options["Raw"]) + { + return item; + } + + StringBuilder sb = new StringBuilder(); + string logFormat = options.ContainsKey("LogFormat") ? options["LogFormat"].ToString().ToLowerInvariant() : "combined"; + + switch (logFormat) + { + case "extended": + return sb.Append("Date: ").Append(((DateTime)item["Date"]).ToString("yyyy-MM-dd")).Append(" ").Append(((DateTime)item["Date"]).ToString("HH:mm:ss")).Append(" ") + .Append(Sanitize(item["Host"])).Append(" ").Append(Sanitize(item["User"])).Append(" ").Append(Sanitize(((Hashtable)item["Request"])["Method"])) + .Append(" ").Append(Sanitize(((Hashtable)item["Request"])["Resource"])).Append(" ").Append(Sanitize(((Hashtable)item["Request"])["Query"])) + .Append(" ").Append(Sanitize(((Hashtable)item["Response"])["StatusCode"])).Append(" \"").Append(Sanitize(((Hashtable)item["Request"])["Agent"])).Append("\"") + .ToString(); + + case "common": + return sb.Append(Sanitize(item["Host"])).Append(" ").Append(Sanitize(item["RfcUserIdentity"])).Append(" ").Append(Sanitize(item["User"])).Append(" [") + .Append(Regex.Replace(((DateTime)item["Date"]).ToString("dd/MMM/yyyy:HH:mm:ss zzz"), @"([+-]\d{2}):(\d{2})", "$1$2")).Append("] \"") + .Append(Sanitize(((Hashtable)item["Request"])["Method"])).Append(" ").Append(Sanitize(((Hashtable)item["Request"])["Resource"])).Append(" ") + .Append(Sanitize(((Hashtable)item["Request"])["Protocol"])).Append("\" ").Append(Sanitize(((Hashtable)item["Response"])["StatusCode"])) + .Append(" ").Append(Sanitize(((Hashtable)item["Response"])["Size"])).ToString(); + + case "json": + return sb.Append("{\"time\": \"").Append(((DateTime)item["Date"]).ToString("yyyy-MM-ddTHH:mm:ssK")).Append("\",\"remote_ip\": \"") + .Append(Sanitize(item["Host"])).Append("\",\"user\": \"").Append(Sanitize(item["User"])).Append("\",\"method\": \"") + .Append(Sanitize(((Hashtable)item["Request"])["Method"])).Append("\",\"uri\": \"").Append(Sanitize(((Hashtable)item["Request"])["Resource"])) + .Append("\",\"query\": \"").Append(Sanitize(((Hashtable)item["Request"])["Query"])).Append("\",\"status\": ") + .Append(Sanitize(((Hashtable)item["Response"])["StatusCode"])).Append(",\"response_size\": ") + .Append(Sanitize(((Hashtable)item["Response"])["Size"])).Append(",\"user_agent\": \"").Append(Sanitize(((Hashtable)item["Request"])["Agent"])) + .Append("\"}").ToString(); + + // Combined is the default format + default: + return sb.Append(Sanitize(item["Host"])).Append(" ").Append(Sanitize(item["RfcUserIdentity"])).Append(" ").Append(Sanitize(item["User"])).Append(" [") + .Append(Regex.Replace(((DateTime)item["Date"]).ToString("dd/MMM/yyyy:HH:mm:ss zzz"), @"([+-]\d{2}):(\d{2})", "$1$2")).Append("] \"") + .Append(Sanitize(((Hashtable)item["Request"])["Method"])).Append(" ").Append(Sanitize(((Hashtable)item["Request"])["Resource"])).Append(" ") + .Append(Sanitize(((Hashtable)item["Request"])["Protocol"])).Append("\" ").Append(Sanitize(((Hashtable)item["Response"])["StatusCode"])) + .Append(" ").Append(Sanitize(((Hashtable)item["Response"])["Size"])).Append(" \"") + .Append(Sanitize(((Hashtable)item["Request"])["Referrer"])).Append("\" \"").Append(Sanitize(((Hashtable)item["Request"])["Agent"])).Append("\"") + .ToString(); + } + } + + + public static object GeneralLog(Hashtable item, Hashtable options) + { + // Do nothing if the error level isn't present in the options' Levels array + if (options.ContainsKey("Levels") && !((IList)options["Levels"]).Contains(item["Level"])) + { + return null; + } + + // Just return the item if Raw is set + if (options.ContainsKey("Raw") && (bool)options["Raw"]) + { + return item; + } + + // Optimized concatenation using StringBuilder + StringBuilder sb = new StringBuilder(); + return sb.Append("[").Append(((DateTime)item["Date"]).ToString(options["DataFormat"].ToString())).Append("] ") + .Append(item["Level"]).Append(" ").Append(item["Tag"]).Append(" ").Append(item["ThreadId"]).Append(" ").Append(item["Message"]) + .ToString(); + } + + } + +} diff --git a/src/Listener/PodeLogger.cs b/src/Listener/PodeLogger.cs index a614210b4..926bb8b3e 100644 --- a/src/Listener/PodeLogger.cs +++ b/src/Listener/PodeLogger.cs @@ -2,6 +2,8 @@ using System.Collections.Concurrent; using System.Collections; using System.Linq; +using System.Text; +using System.Text.RegularExpressions; namespace Pode { @@ -172,5 +174,11 @@ public static void Clear() Enqueue(logEntry); } } + + + } + + + } diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index 190efc0c9..61adc44ca 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -567,61 +567,15 @@ function Get-PodeLoggingInbuiltType { 'requests' { $script = { param($item, $options) - - # Just return the item if Raw is set - if ($options.Raw) { - return $item - } - - # Helper function to sanitize and return a default value if the input is null or whitespace - function sg($value) { - if ([string]::IsNullOrWhiteSpace($value)) { - return '-' - } - return $value - } - - switch ($options.LogFormat.ToLowerInvariant()) { - 'extended' { - return [System.Text.StringBuilder]::new(). - $sb.Append('Date: ').Append($item.Date.ToString('yyyy-MM-dd')).Append(' ').Append($item.Date.ToString('HH:mm:ss')).Append(' '). - $sb.Append((sg $item.Host)).Append(' ').Append((sg $item.User)).Append(' ').Append((sg $item.Request.Method)).Append(' '). - $sb.Append((sg $item.Request.Resource)).Append(' ').Append((sg $item.Request.Query)).Append(' ').Append((sg $item.Response.StatusCode)). - $sb.Append(' ').Append((sg $item.Response.Size)).Append(' "').Append((sg $item.Request.Agent)).Append('"').ToString() - } - 'common' { - return [System.Text.StringBuilder]::new() - Append((sg $item.Host)).Append(' ').Append((sg $item.RfcUserIdentity)).Append(' ').Append((sg $item.User)).Append(' ['). - Append(([regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2'))).Append('] "'). - Append((sg $item.Request.Method)).Append(' ').Append((sg $item.Request.Resource)).Append(' ').Append((sg $item.Request.Protocol)). - Append('" ').Append((sg $item.Response.StatusCode)).Append(' ').Append((sg $item.Response.Size)).ToString() - } - 'json' { - return [System.Text.StringBuilder]::new(). - Append('{"time": "').Append($item.Date.ToString('yyyy-MM-ddTHH:mm:ssK')).Append('","remote_ip": "').Append((sg $item.Host)). - Append('","user": "').Append((sg $item.User)).Append('","method": "').Append((sg $item.Request.Method)).Append('","uri": "'). - Append((sg $item.Request.Resource)).Append('","query": "').Append((sg $item.Request.Query)).Append('","status": '). - Append((sg $item.Response.StatusCode)).Append(',"response_size": ').Append((sg $item.Response.Size)). - Append(',"user_agent": "').Append((sg $item.Request.Agent)).Append('"}').ToString() - } - # Combined is the default - default { - return [System.Text.StringBuilder]::new().Append((sg $item.Host)).Append(' ').Append((sg $item.RfcUserIdentity)).Append(' ').Append((sg $item.User)). - Append(' [').Append(([regex]::Replace(($item.Date.ToString('dd/MMM/yyyy:HH:mm:ss zzz')), '([+-]\d{2}):(\d{2})', '$1$2'))). - Append('] "').Append((sg $item.Request.Method)).Append(' ').Append((sg $item.Request.Resource)).Append(' '). - Append((sg $item.Request.Protocol)).Append('" ').Append((sg $item.Response.StatusCode)).Append(' ').Append((sg $item.Response.Size)). - Append(' "').Append((sg $item.Request.Referrer)).Append('" "').Append((sg $item.Request.Agent)).Append('"').ToString() - } - } - return $item + return [Pode.PodeFormat]::RequestLog($item, $options) } } 'errors' { $script = { param($item, $options) - - # Do nothing if the error level isn't present + return [Pode.PodeFormat]::ErrorsLog($item, $options) + <# # Do nothing if the error level isn't present if (@($options.Levels) -inotcontains $item.Level) { return } @@ -635,14 +589,14 @@ function Get-PodeLoggingInbuiltType { Append('Date: ').Append($item.Date.ToString($options.DataFormat)).Append('Level: ').Append($item.Level). Append('ThreadId: ').Append($item.ThreadId).Append('Server: ').Append($item.Server).Append('Category: '). Append($item.Category).Append('Message: ').Append($item.Message).Append('StackTrace: ').Append($item.StackTrace).ToString() - +#> } } 'general' { $script = { param($item, $options) - - # Do nothing if the error level isn't present + return [Pode.PodeFormat]::GeneralLog($item, $options) + <# # Do nothing if the error level isn't present if (@($options.Levels) -inotcontains $item.Level) { return } @@ -655,14 +609,15 @@ function Get-PodeLoggingInbuiltType { return [System.Text.StringBuilder]::new(). Append('[').Append($item.Date.ToString($options.DataFormat)).Append('] '). Append($item.Level).Append(' ').Append($item.Tag).Append(' ').Append($item.ThreadId).Append(' ').Append($item.Message).ToString() + #> } } 'Default' { $script = { param($item, $options) - - # Just return the item if Raw is set + return [Pode.PodeFormat]::GeneralLog($item, $options) + <# # Just return the item if Raw is set if ($options.Raw) { return $item } @@ -670,6 +625,7 @@ function Get-PodeLoggingInbuiltType { return [System.Text.StringBuilder]::new(). Append('[').Append($item.Date.ToString($options.DataFormat)).Append('] '). Append($item.Level).Append(' ').Append($item.Tag).Append(' ').Append($item.ThreadId).Append(' ').Append($item.Message).ToString() + #> } } } @@ -1340,7 +1296,7 @@ function Enable-PodeLoggingInternal { Method = $Method ScriptBlock = $scriptBlock Arguments = @{ - Raw = $Raw + Raw = $Raw.IsPresent Levels = $Levels DataFormat = $Method.Arguments.DataFormat } diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 475bb2591..6ac045c1b 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -298,7 +298,7 @@ function Enable-PodeRequestLogging { Username = $UsernameProperty } Arguments = @{ - Raw = $Raw + Raw = $Raw.IsPresent DataFormat = $Method.Arguments.DataFormat LogFormat = $LogFormat } @@ -512,7 +512,7 @@ function Add-PodeLoggingMethod { Method = $Method ScriptBlock = (Get-PodeLoggingInbuiltType -Type General) Arguments = @{ - Raw = $Raw + Raw = $Raw.IsPresent Levels = $Levels DataFormat = $Method.Arguments.DataFormat } From b529180e3342f19e27c1bba0e732f00041dc6542 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 2 Nov 2024 08:28:47 -0700 Subject: [PATCH 30/53] FIx extended format --- examples/Logging.ps1 | 2 +- src/Listener/PodeFormat.cs | 151 ++++++++++++++++++++++++++----------- src/Private/Logging.ps1 | 38 ---------- 3 files changed, 106 insertions(+), 85 deletions(-) diff --git a/examples/Logging.ps1 b/examples/Logging.ps1 index 23b3b7edf..979c16ce9 100644 --- a/examples/Logging.ps1 +++ b/examples/Logging.ps1 @@ -91,7 +91,7 @@ Start-PodeServer -browse { throw 'No logging selected' } if ( $requestLogging) { - $requestLogging | Enable-PodeRequestLogging + $requestLogging | Enable-PodeRequestLogging -LogFormat Extended } New-PodeFileLoggingMethod -Name 'error' -MaxDays 4 -Format RFC5424 -ISO8601 | Enable-PodeErrorLogging -Raw -Levels Error diff --git a/src/Listener/PodeFormat.cs b/src/Listener/PodeFormat.cs index 2f6b16b4e..665f69249 100644 --- a/src/Listener/PodeFormat.cs +++ b/src/Listener/PodeFormat.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Concurrent; using System.Collections; -using System.Linq; using System.Text; using System.Text.RegularExpressions; @@ -9,104 +7,165 @@ namespace Pode { public static class PodeFormat { - // Helper function to sanitize and return a default value if the input is null or whitespace + /// + /// Helper function to sanitize input by returning a default value if the input is null or whitespace. + /// + /// The object value to be sanitized. + /// A sanitized string, or "-" if the input is null or whitespace. private static string Sanitize(object value) { return value == null || string.IsNullOrWhiteSpace(value.ToString()) ? "-" : value.ToString(); } + /// + /// Formats error log entries based on the provided options. Includes details like Date, Level, ThreadId, Server, Category, Message, and StackTrace. + /// + /// A hashtable containing log details. + /// A hashtable containing format options such as Levels, Raw, and DataFormat. + /// A formatted log string, or the original item if Raw is specified, or null if Level is not in options.Levels. public static object ErrorsLog(Hashtable item, Hashtable options) { - // Do nothing if the error level isn't present in the options' Levels array + // Check if required keys are present and valid + if (item == null || options == null) return null; + if (!item.ContainsKey("Level") || !item.ContainsKey("Date") || !item.ContainsKey("ThreadId") || + !item.ContainsKey("Server") || !item.ContainsKey("Category") || !item.ContainsKey("Message") || !item.ContainsKey("StackTrace")) + { + return null; + } + + // Check if the error level is present in the options' Levels array if (options.ContainsKey("Levels") && !((IList)options["Levels"]).Contains(item["Level"])) { return null; } - // Just return the item if Raw is set + // Return the raw item if Raw is set if (options.ContainsKey("Raw") && (bool)options["Raw"]) { return item; } - // Optimized concatenation using StringBuilder + // Retrieve data format or default to a basic date format + string dataFormat = options.ContainsKey("DataFormat") ? options["DataFormat"].ToString() : "yyyy-MM-dd HH:mm:ss"; + + // Construct the log entry with specified fields StringBuilder sb = new StringBuilder(); - return sb.Append("Date: ").Append(((DateTime)item["Date"]).ToString(options["DataFormat"].ToString())).Append(" Level: ").Append(item["Level"]) - .Append(" ThreadId: ").Append(item["ThreadId"]).Append(" Server: ").Append(item["Server"]).Append(" Category: ") - .Append(item["Category"]).Append(" Message: ").Append(item["Message"]).Append(" StackTrace: ").Append(item["StackTrace"]) - .ToString(); + return sb.Append("Date: ").Append(((DateTime)item["Date"]).ToString(dataFormat)).Append(" Level: ").Append(Sanitize(item["Level"])) + .Append(" ThreadId: ").Append(Sanitize(item["ThreadId"])).Append(" Server: ").Append(Sanitize(item["Server"])).Append(" Category: ") + .Append(Sanitize(item["Category"])).Append(" Message: ").Append(Sanitize(item["Message"])).Append(" StackTrace: ") + .Append(Sanitize(item["StackTrace"])).ToString(); } + /// + /// Formats request log entries based on the provided log format in options. Supports "extended", "common", "json", and "combined" formats. + /// + /// A hashtable containing request log details. + /// A hashtable containing format options such as LogFormat and Raw. + /// A formatted request log string, or the original item if Raw is specified. public static object RequestLog(Hashtable item, Hashtable options) { - // Just return the item if Raw is set + if (item == null || options == null) return null; + + // Return the raw item if Raw is set if (options.ContainsKey("Raw") && (bool)options["Raw"]) { return item; } - StringBuilder sb = new StringBuilder(); + // Retrieve log format or default to "combined" string logFormat = options.ContainsKey("LogFormat") ? options["LogFormat"].ToString().ToLowerInvariant() : "combined"; + // Construct the log entry based on the specified format + StringBuilder sb = new StringBuilder(); + switch (logFormat) { case "extended": - return sb.Append("Date: ").Append(((DateTime)item["Date"]).ToString("yyyy-MM-dd")).Append(" ").Append(((DateTime)item["Date"]).ToString("HH:mm:ss")).Append(" ") - .Append(Sanitize(item["Host"])).Append(" ").Append(Sanitize(item["User"])).Append(" ").Append(Sanitize(((Hashtable)item["Request"])["Method"])) - .Append(" ").Append(Sanitize(((Hashtable)item["Request"])["Resource"])).Append(" ").Append(Sanitize(((Hashtable)item["Request"])["Query"])) - .Append(" ").Append(Sanitize(((Hashtable)item["Response"])["StatusCode"])).Append(" \"").Append(Sanitize(((Hashtable)item["Request"])["Agent"])).Append("\"") - .ToString(); + if (item.ContainsKey("Host") && item.ContainsKey("User") && item.ContainsKey("Request") && item.ContainsKey("Response") && + item["Request"] is Hashtable requestExtended && item["Response"] is Hashtable responseExtended) + { + return sb.Append(((DateTime)item["Date"]).ToString("yyyy-MM-dd")).Append(" ").Append(((DateTime)item["Date"]).ToString("HH:mm:ss")).Append(" ") + .Append(Sanitize(item["Host"])).Append(" ").Append(Sanitize(item["RfcUserIdentity"])).Append(" ") + .Append(Sanitize(item["User"])).Append(" ").Append(Sanitize(requestExtended["Method"])).Append(" ") + .Append(Sanitize(requestExtended["Resource"])).Append(" ").Append("- ").Append(Sanitize(responseExtended["StatusCode"])).Append(" ") + .Append(Sanitize(responseExtended["Size"])).Append(" ").Append("\"").Append(Sanitize(requestExtended["Agent"])).Append("\"") + .ToString(); + } + break; case "common": - return sb.Append(Sanitize(item["Host"])).Append(" ").Append(Sanitize(item["RfcUserIdentity"])).Append(" ").Append(Sanitize(item["User"])).Append(" [") - .Append(Regex.Replace(((DateTime)item["Date"]).ToString("dd/MMM/yyyy:HH:mm:ss zzz"), @"([+-]\d{2}):(\d{2})", "$1$2")).Append("] \"") - .Append(Sanitize(((Hashtable)item["Request"])["Method"])).Append(" ").Append(Sanitize(((Hashtable)item["Request"])["Resource"])).Append(" ") - .Append(Sanitize(((Hashtable)item["Request"])["Protocol"])).Append("\" ").Append(Sanitize(((Hashtable)item["Response"])["StatusCode"])) - .Append(" ").Append(Sanitize(((Hashtable)item["Response"])["Size"])).ToString(); + if (item.ContainsKey("Host") && item.ContainsKey("RfcUserIdentity") && item.ContainsKey("User") && item.ContainsKey("Request") && item.ContainsKey("Response") && + item["Request"] is Hashtable requestCommon && item["Response"] is Hashtable responseCommon) + { + return sb.Append(Sanitize(item["Host"])).Append(" ").Append(Sanitize(item["RfcUserIdentity"])).Append(" ").Append(Sanitize(item["User"])).Append(" [") + .Append(Regex.Replace(((DateTime)item["Date"]).ToString("dd/MMM/yyyy:HH:mm:ss zzz"), @"([+-]\d{2}):(\d{2})", "$1$2")).Append("] \"") + .Append(Sanitize(requestCommon["Method"])).Append(" ").Append(Sanitize(requestCommon["Resource"])).Append(" ") + .Append(Sanitize(requestCommon["Protocol"])).Append("\" ").Append(Sanitize(responseCommon["StatusCode"])) + .Append(" ").Append(Sanitize(responseCommon["Size"])).ToString(); + } + break; case "json": - return sb.Append("{\"time\": \"").Append(((DateTime)item["Date"]).ToString("yyyy-MM-ddTHH:mm:ssK")).Append("\",\"remote_ip\": \"") - .Append(Sanitize(item["Host"])).Append("\",\"user\": \"").Append(Sanitize(item["User"])).Append("\",\"method\": \"") - .Append(Sanitize(((Hashtable)item["Request"])["Method"])).Append("\",\"uri\": \"").Append(Sanitize(((Hashtable)item["Request"])["Resource"])) - .Append("\",\"query\": \"").Append(Sanitize(((Hashtable)item["Request"])["Query"])).Append("\",\"status\": ") - .Append(Sanitize(((Hashtable)item["Response"])["StatusCode"])).Append(",\"response_size\": ") - .Append(Sanitize(((Hashtable)item["Response"])["Size"])).Append(",\"user_agent\": \"").Append(Sanitize(((Hashtable)item["Request"])["Agent"])) - .Append("\"}").ToString(); - - // Combined is the default format + if (item.ContainsKey("Host") && item.ContainsKey("User") && item.ContainsKey("Request") && item.ContainsKey("Response") && + item["Request"] is Hashtable requestJson && item["Response"] is Hashtable responseJson) + { + return sb.Append("{\"time\": \"").Append(((DateTime)item["Date"]).ToString("yyyy-MM-ddTHH:mm:ssK")).Append("\",\"remote_ip\": \"") + .Append(Sanitize(item["Host"])).Append("\",\"user\": \"").Append(Sanitize(item["User"])).Append("\",\"method\": \"") + .Append(Sanitize(requestJson["Method"])).Append("\",\"uri\": \"").Append(Sanitize(requestJson["Resource"])) + .Append("\",\"query\": \"").Append(Sanitize(requestJson["Query"])).Append("\",\"status\": ") + .Append(Sanitize(responseJson["StatusCode"])).Append(",\"response_size\": ") + .Append(Sanitize(responseJson["Size"])).Append(",\"user_agent\": \"").Append(Sanitize(requestJson["Agent"])) + .Append("\"}").ToString(); + } + break; + default: - return sb.Append(Sanitize(item["Host"])).Append(" ").Append(Sanitize(item["RfcUserIdentity"])).Append(" ").Append(Sanitize(item["User"])).Append(" [") - .Append(Regex.Replace(((DateTime)item["Date"]).ToString("dd/MMM/yyyy:HH:mm:ss zzz"), @"([+-]\d{2}):(\d{2})", "$1$2")).Append("] \"") - .Append(Sanitize(((Hashtable)item["Request"])["Method"])).Append(" ").Append(Sanitize(((Hashtable)item["Request"])["Resource"])).Append(" ") - .Append(Sanitize(((Hashtable)item["Request"])["Protocol"])).Append("\" ").Append(Sanitize(((Hashtable)item["Response"])["StatusCode"])) - .Append(" ").Append(Sanitize(((Hashtable)item["Response"])["Size"])).Append(" \"") - .Append(Sanitize(((Hashtable)item["Request"])["Referrer"])).Append("\" \"").Append(Sanitize(((Hashtable)item["Request"])["Agent"])).Append("\"") - .ToString(); + if (item.ContainsKey("Host") && item.ContainsKey("RfcUserIdentity") && item.ContainsKey("User") && item.ContainsKey("Request") && item.ContainsKey("Response") && + item["Request"] is Hashtable requestCombined && item["Response"] is Hashtable responseCombined) + { + return sb.Append(Sanitize(item["Host"])).Append(" ").Append(Sanitize(item["RfcUserIdentity"])).Append(" ").Append(Sanitize(item["User"])).Append(" [") + .Append(Regex.Replace(((DateTime)item["Date"]).ToString("dd/MMM/yyyy:HH:mm:ss zzz"), @"([+-]\d{2}):(\d{2})", "$1$2")).Append("] \"") + .Append(Sanitize(requestCombined["Method"])).Append(" ").Append(Sanitize(requestCombined["Resource"])).Append(" ") + .Append(Sanitize(requestCombined["Protocol"])).Append("\" ").Append(Sanitize(responseCombined["StatusCode"])) + .Append(" ").Append(Sanitize(responseCombined["Size"])).Append(" \"") + .Append(Sanitize(requestCombined["Referrer"])).Append("\" \"").Append(Sanitize(requestCombined["Agent"])).Append("\"") + .ToString(); + } + break; } + return null; } - + /// + /// Formats general log entries, checking for level filtering and the presence of required fields. + /// + /// A hashtable containing general log details. + /// A hashtable containing format options such as Levels, Raw, and DataFormat. + /// A formatted general log string, or the original item if Raw is specified, or null if Level is not in options.Levels. public static object GeneralLog(Hashtable item, Hashtable options) { - // Do nothing if the error level isn't present in the options' Levels array + if (item == null || options == null) return null; + + // Check if the error level is present in the options' Levels array if (options.ContainsKey("Levels") && !((IList)options["Levels"]).Contains(item["Level"])) { return null; } - // Just return the item if Raw is set + // Return the raw item if Raw is set if (options.ContainsKey("Raw") && (bool)options["Raw"]) { return item; } - // Optimized concatenation using StringBuilder + // Retrieve data format or default to a basic date format + string dataFormat = options.ContainsKey("DataFormat") ? options["DataFormat"].ToString() : "yyyy-MM-dd HH:mm:ss"; + + // Construct the log entry with specified fields StringBuilder sb = new StringBuilder(); - return sb.Append("[").Append(((DateTime)item["Date"]).ToString(options["DataFormat"].ToString())).Append("] ") - .Append(item["Level"]).Append(" ").Append(item["Tag"]).Append(" ").Append(item["ThreadId"]).Append(" ").Append(item["Message"]) + return sb.Append("[").Append(((DateTime)item["Date"]).ToString(dataFormat)).Append("] ") + .Append(Sanitize(item["Level"])).Append(" ").Append(Sanitize(item["Tag"])).Append(" ").Append(Sanitize(item["ThreadId"])).Append(" ").Append(Sanitize(item["Message"])) .ToString(); } - } - } diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index 61adc44ca..273fcf85b 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -575,41 +575,12 @@ function Get-PodeLoggingInbuiltType { $script = { param($item, $options) return [Pode.PodeFormat]::ErrorsLog($item, $options) - <# # Do nothing if the error level isn't present - if (@($options.Levels) -inotcontains $item.Level) { - return - } - - # Just return the item if Raw is set - if ($options.Raw) { - return $item - } - # Optimized concatenation using Append - return [System.Text.StringBuilder]::new(). - Append('Date: ').Append($item.Date.ToString($options.DataFormat)).Append('Level: ').Append($item.Level). - Append('ThreadId: ').Append($item.ThreadId).Append('Server: ').Append($item.Server).Append('Category: '). - Append($item.Category).Append('Message: ').Append($item.Message).Append('StackTrace: ').Append($item.StackTrace).ToString() -#> } } 'general' { $script = { param($item, $options) return [Pode.PodeFormat]::GeneralLog($item, $options) - <# # Do nothing if the error level isn't present - if (@($options.Levels) -inotcontains $item.Level) { - return - } - - # Just return the item if Raw is set - if ($options.Raw) { - return $item - } - # Optimized concatenation using Append - return [System.Text.StringBuilder]::new(). - Append('[').Append($item.Date.ToString($options.DataFormat)).Append('] '). - Append($item.Level).Append(' ').Append($item.Tag).Append(' ').Append($item.ThreadId).Append(' ').Append($item.Message).ToString() - #> } } @@ -617,15 +588,6 @@ function Get-PodeLoggingInbuiltType { $script = { param($item, $options) return [Pode.PodeFormat]::GeneralLog($item, $options) - <# # Just return the item if Raw is set - if ($options.Raw) { - return $item - } - # Optimized concatenation using Append - return [System.Text.StringBuilder]::new(). - Append('[').Append($item.Date.ToString($options.DataFormat)).Append('] '). - Append($item.Level).Append(' ').Append($item.Tag).Append(' ').Append($item.ThreadId).Append(' ').Append($item.Message).ToString() - #> } } } From 6119380acde1d82aee01080595a54f675635b765 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 2 Nov 2024 09:44:23 -0700 Subject: [PATCH 31/53] Syslog format in C# --- src/Listener/PodeFormat.cs | 109 ++++++++++++++++++++--- src/Listener/PodeLogger.cs | 134 ++++++++++++++++++++-------- src/Private/Logging.ps1 | 163 +++-------------------------------- src/Public/Logging.ps1 | 31 +------ src/Public/LoggingMethod.ps1 | 18 ++-- 5 files changed, 216 insertions(+), 239 deletions(-) diff --git a/src/Listener/PodeFormat.cs b/src/Listener/PodeFormat.cs index 665f69249..99e77dc6a 100644 --- a/src/Listener/PodeFormat.cs +++ b/src/Listener/PodeFormat.cs @@ -84,11 +84,11 @@ public static object RequestLog(Hashtable item, Hashtable options) if (item.ContainsKey("Host") && item.ContainsKey("User") && item.ContainsKey("Request") && item.ContainsKey("Response") && item["Request"] is Hashtable requestExtended && item["Response"] is Hashtable responseExtended) { - return sb.Append(((DateTime)item["Date"]).ToString("yyyy-MM-dd")).Append(" ").Append(((DateTime)item["Date"]).ToString("HH:mm:ss")).Append(" ") - .Append(Sanitize(item["Host"])).Append(" ").Append(Sanitize(item["RfcUserIdentity"])).Append(" ") - .Append(Sanitize(item["User"])).Append(" ").Append(Sanitize(requestExtended["Method"])).Append(" ") - .Append(Sanitize(requestExtended["Resource"])).Append(" ").Append("- ").Append(Sanitize(responseExtended["StatusCode"])).Append(" ") - .Append(Sanitize(responseExtended["Size"])).Append(" ").Append("\"").Append(Sanitize(requestExtended["Agent"])).Append("\"") + return sb.Append(((DateTime)item["Date"]).ToString("yyyy-MM-dd")).Append(' ').Append(((DateTime)item["Date"]).ToString("HH:mm:ss")).Append(' ') + .Append(Sanitize(item["Host"])).Append(' ').Append(Sanitize(item["RfcUserIdentity"])).Append(' ') + .Append(Sanitize(item["User"])).Append(' ').Append(Sanitize(requestExtended["Method"])).Append(' ') + .Append(Sanitize(requestExtended["Resource"])).Append(' ').Append("- ").Append(Sanitize(responseExtended["StatusCode"])).Append(' ') + .Append(Sanitize(responseExtended["Size"])).Append(' ').Append("\"").Append(Sanitize(requestExtended["Agent"])).Append("\"") .ToString(); } break; @@ -97,11 +97,11 @@ public static object RequestLog(Hashtable item, Hashtable options) if (item.ContainsKey("Host") && item.ContainsKey("RfcUserIdentity") && item.ContainsKey("User") && item.ContainsKey("Request") && item.ContainsKey("Response") && item["Request"] is Hashtable requestCommon && item["Response"] is Hashtable responseCommon) { - return sb.Append(Sanitize(item["Host"])).Append(" ").Append(Sanitize(item["RfcUserIdentity"])).Append(" ").Append(Sanitize(item["User"])).Append(" [") + return sb.Append(Sanitize(item["Host"])).Append(' ').Append(Sanitize(item["RfcUserIdentity"])).Append(' ').Append(Sanitize(item["User"])).Append(" [") .Append(Regex.Replace(((DateTime)item["Date"]).ToString("dd/MMM/yyyy:HH:mm:ss zzz"), @"([+-]\d{2}):(\d{2})", "$1$2")).Append("] \"") - .Append(Sanitize(requestCommon["Method"])).Append(" ").Append(Sanitize(requestCommon["Resource"])).Append(" ") + .Append(Sanitize(requestCommon["Method"])).Append(' ').Append(Sanitize(requestCommon["Resource"])).Append(' ') .Append(Sanitize(requestCommon["Protocol"])).Append("\" ").Append(Sanitize(responseCommon["StatusCode"])) - .Append(" ").Append(Sanitize(responseCommon["Size"])).ToString(); + .Append(' ').Append(Sanitize(responseCommon["Size"])).ToString(); } break; @@ -123,11 +123,11 @@ public static object RequestLog(Hashtable item, Hashtable options) if (item.ContainsKey("Host") && item.ContainsKey("RfcUserIdentity") && item.ContainsKey("User") && item.ContainsKey("Request") && item.ContainsKey("Response") && item["Request"] is Hashtable requestCombined && item["Response"] is Hashtable responseCombined) { - return sb.Append(Sanitize(item["Host"])).Append(" ").Append(Sanitize(item["RfcUserIdentity"])).Append(" ").Append(Sanitize(item["User"])).Append(" [") + return sb.Append(Sanitize(item["Host"])).Append(' ').Append(Sanitize(item["RfcUserIdentity"])).Append(' ').Append(Sanitize(item["User"])).Append(" [") .Append(Regex.Replace(((DateTime)item["Date"]).ToString("dd/MMM/yyyy:HH:mm:ss zzz"), @"([+-]\d{2}):(\d{2})", "$1$2")).Append("] \"") - .Append(Sanitize(requestCombined["Method"])).Append(" ").Append(Sanitize(requestCombined["Resource"])).Append(" ") + .Append(Sanitize(requestCombined["Method"])).Append(' ').Append(Sanitize(requestCombined["Resource"])).Append(' ') .Append(Sanitize(requestCombined["Protocol"])).Append("\" ").Append(Sanitize(responseCombined["StatusCode"])) - .Append(" ").Append(Sanitize(responseCombined["Size"])).Append(" \"") + .Append(' ').Append(Sanitize(responseCombined["Size"])).Append(" \"") .Append(Sanitize(requestCombined["Referrer"])).Append("\" \"").Append(Sanitize(requestCombined["Agent"])).Append("\"") .ToString(); } @@ -163,9 +163,92 @@ public static object GeneralLog(Hashtable item, Hashtable options) // Construct the log entry with specified fields StringBuilder sb = new StringBuilder(); - return sb.Append("[").Append(((DateTime)item["Date"]).ToString(dataFormat)).Append("] ") - .Append(Sanitize(item["Level"])).Append(" ").Append(Sanitize(item["Tag"])).Append(" ").Append(Sanitize(item["ThreadId"])).Append(" ").Append(Sanitize(item["Message"])) + return sb.Append('[').Append(((DateTime)item["Date"]).ToString(dataFormat)).Append("] ") + .Append(Sanitize(item["Level"])).Append(' ').Append(Sanitize(item["Tag"])).Append(' ').Append(Sanitize(item["ThreadId"])).Append(' ').Append(Sanitize(item["Message"])) .ToString(); } + + + + + public static string Syslog(Hashtable rawItem, Hashtable options, Hashtable masking) + { + int maxLength = -1; + string message = string.Empty; + + // Process Message and StackTrace + if (rawItem.ContainsKey("Message")) + { + if (rawItem.ContainsKey("StackTrace") && !string.IsNullOrEmpty(rawItem["StackTrace"] as string)) + { + message = $"{rawItem["Level"].ToString().ToUpperInvariant()}: {PodeLogger.ProtectLogItem(rawItem["Message"].ToString(), masking)}. Exception Type: {rawItem["Category"]}. Stack Trace: {rawItem["StackTrace"]}"; + } + else + { + message = PodeLogger.ProtectLogItem(rawItem["Message"].ToString(), masking); + } + } + + // Map Level to Syslog Severity + int severity; + string level = rawItem["Level"].ToString().ToLowerInvariant(); + switch (level) + { + case "emergency": severity = 0; break; + case "alert": severity = 1; break; + case "critical": severity = 2; break; + case "error": severity = 3; break; + case "warning": severity = 4; break; + case "notice": severity = 5; break; + case "info": + case "informational": severity = 6; break; + case "debug": severity = 7; break; + default: severity = 6; break; // Default to Informational + } + + // Define Tag + string tag = string.IsNullOrEmpty(rawItem["Tag"] as string) + ? (options != null && options.ContainsKey("DefaultTag") ? options["DefaultTag"].ToString() : "DefaultTag") + : rawItem["Tag"].ToString(); + + // Define Facility and Priority + int facility = 1; // User-level messages + int priority = (facility * 8) + severity; + int processId = System.Diagnostics.Process.GetCurrentProcess().Id; + string fullSyslogMessage; + string timestamp; + + // Determine Syslog Message Format + switch (options["Format"].ToString().ToUpper()) + { + case "RFC3164": + maxLength = 1024; + timestamp = ((DateTime)rawItem["Date"]).ToString("MMM dd HH:mm:ss"); + fullSyslogMessage = string.Format("<{0}>{1} {2} {3}: {4}", priority, timestamp, Environment.MachineName, tag, message); + break; + + case "RFC5424": + maxLength = 2048; + timestamp = ((DateTime)rawItem["Date"]).ToString("yyyy-MM-ddTHH:mm:ss.ffffffK"); + fullSyslogMessage = string.Format("<{0}>1 {1} {2} {3} {4} - - {5}", priority, timestamp, Environment.MachineName, tag, processId, message); + break; + + default: + maxLength = options != null && options.ContainsKey("MaxLength") ? Convert.ToInt32(options["MaxLength"]) : -1; + string dataFormat = options != null && options.ContainsKey("DataFormat") ? options["DataFormat"].ToString() : null; + string separator = options != null && options.ContainsKey("Separator") ? options["Separator"].ToString() : " "; + timestamp = !string.IsNullOrEmpty(dataFormat) ? ((DateTime)rawItem["Date"]).ToString(dataFormat) : string.Empty; + fullSyslogMessage = string.Format("{0}{1}{2}{3}{4}{5}{6}", timestamp, separator, rawItem["Level"], separator, tag, separator, message); + break; + } + + // Truncate message if it exceeds maxLength + if (maxLength > 0 && fullSyslogMessage.Length > maxLength) + { + return fullSyslogMessage.Substring(0, maxLength); + } + + return fullSyslogMessage; + } } } diff --git a/src/Listener/PodeLogger.cs b/src/Listener/PodeLogger.cs index 926bb8b3e..4dd44f1c9 100644 --- a/src/Listener/PodeLogger.cs +++ b/src/Listener/PodeLogger.cs @@ -2,22 +2,24 @@ using System.Collections.Concurrent; using System.Collections; using System.Linq; -using System.Text; using System.Text.RegularExpressions; namespace Pode { public static class PodeLogger { - // Static fields to store the logging status and the queue of log entries + // Static fields to control logging and store log entries in a thread-safe queue private static bool _enabled; private static ConcurrentQueue _queue; - // Static property to enable or disable writing logs to the console + /// + /// Enables or disables writing logs to the console. + /// public static bool Terminal { get; set; } - - // Static property to enable or disable logging + /// + /// Enables or disables logging. Initializes or clears the queue based on the value. + /// public static bool Enabled { get => _enabled; @@ -26,24 +28,26 @@ public static bool Enabled _enabled = value; if (_enabled) { - // Initialize the queue if logging is enabled + // Initializes the queue for logging _queue = new ConcurrentQueue(); } else { - // Clear the queue if logging is disabled + // Clears the queue if logging is disabled _queue = null; } } } - // Property to get the count of items in the queue - public static int Count - { - get => _queue != null ? _queue.Count : 0; - } + /// + /// Gets the count of items in the log queue. + /// + public static int Count => _queue != null ? _queue.Count : 0; - // Method to add a Hashtable to the queue + /// + /// Adds a log entry to the queue. + /// + /// The log entry as a Hashtable. public static void Enqueue(Hashtable table) { if (_queue != null) @@ -52,7 +56,11 @@ public static void Enqueue(Hashtable table) } } - // Method to try and dequeue a Hashtable from the queue + /// + /// Attempts to dequeue a log entry from the queue. + /// + /// The dequeued log entry. + /// True if a log entry was dequeued, false otherwise. public static bool TryDequeue(out Hashtable table) { if (_queue != null) @@ -63,7 +71,10 @@ public static bool TryDequeue(out Hashtable table) return false; } - // Method to dequeue a Hashtable from the queue + /// + /// Dequeues a log entry from the queue. Returns null if the queue is empty. + /// + /// The dequeued log entry as a Hashtable. public static Hashtable Dequeue() { if (_queue != null && _queue.TryDequeue(out Hashtable table)) @@ -73,32 +84,37 @@ public static Hashtable Dequeue() return null; } - // Method to clear the queue + /// + /// Clears all entries from the log queue. + /// public static void Clear() { if (_queue != null) { - if (_queue != null) - { - while (_queue.TryDequeue(out _)) { } - } + while (_queue.TryDequeue(out _)) { } } } - // Method to log an exception + + /// + /// Logs an exception by adding it to the queue and optionally writing it to the console. + /// + /// The exception to log. + /// Optional PodeConnector to control logging based on settings. + /// The logging level (default is Error). public static void LogException(Exception ex, PodeConnector connector = default(PodeConnector), PodeLoggingLevel level = PodeLoggingLevel.Error) { - if (ex == default(Exception)) + if (ex == null) { return; } - // Return if logging is disabled, or if the level isn't being logged - if (connector != default(PodeConnector) && (!connector.ErrorLoggingEnabled || !connector.ErrorLoggingLevels.Contains(level.ToString(), StringComparer.InvariantCultureIgnoreCase))) + // Exit if logging is disabled or the logging level isn’t configured in the connector + if (connector != null && (!connector.ErrorLoggingEnabled || !connector.ErrorLoggingLevels.Contains(level.ToString(), StringComparer.InvariantCultureIgnoreCase))) { return; } - // Write the exception to the console if Terminal is enabled + // If Terminal logging is enabled, output exception details to the console if (Terminal) { Console.WriteLine($"[{level}] {ex.GetType().Name}: {ex.Message}"); @@ -124,25 +140,31 @@ public static void Clear() } } - // Method to log a message + /// + /// Logs a message by adding it to the queue and optionally writing it to the console. + /// + /// The message to log. + /// Optional PodeConnector to control logging based on settings. + /// The logging level (default is Error). + /// Optional PodeContext to include context ID in the log entry. public static void LogMessage(string message, PodeConnector connector = default(PodeConnector), PodeLoggingLevel level = PodeLoggingLevel.Error, PodeContext context = default(PodeContext)) { - // Do nothing if the message is empty or whitespace + // Exit if message is empty or whitespace if (string.IsNullOrWhiteSpace(message)) { return; } - // Return if logging is disabled, or if the level isn't being logged - if (connector != default(PodeConnector) && (!connector.ErrorLoggingEnabled || !connector.ErrorLoggingLevels.Contains(level.ToString(), StringComparer.InvariantCultureIgnoreCase))) + // Exit if logging is disabled or the level isn’t configured in the connector + if (connector != null && (!connector.ErrorLoggingEnabled || !connector.ErrorLoggingLevels.Contains(level.ToString(), StringComparer.InvariantCultureIgnoreCase))) { return; } - // Write the message to the console if Terminal is enabled + // If Terminal logging is enabled, output message to the console, including context ID if provided if (Terminal) { - if (context == default(PodeContext)) + if (context == null) { Console.WriteLine($"[{level}]: {message}"); } @@ -152,7 +174,7 @@ public static void Clear() } } - // Add the error message to the log queue if logging is enabled + // Add the log message to the log queue if logging is enabled if (Enabled) { Hashtable logEntry = new Hashtable @@ -166,6 +188,7 @@ public static void Clear() } }; + // Add the context ID to the log entry if a context is provided if (context != null) { ((Hashtable)logEntry["Item"])["TargetObject"] = context.ID; @@ -175,10 +198,53 @@ public static void Clear() } } + /// + /// Masks sensitive information in a log item based on specified regex patterns. + /// + /// The log item to mask. + /// A Hashtable containing masking patterns and a mask character. + /// The masked log item as a string. + public static string ProtectLogItem(string item, Hashtable masking) + { + // Exit if there are no masking patterns + if (masking == null || masking.Count == 0) + { + return item; + } - - } + // Retrieve the mask character and patterns from the masking hashtable + string mask = masking["Mask"].ToString(); + object[] patterns = (object[])masking["Patterns"]; + // Apply each regex pattern to the log item + foreach (string regexPattern in patterns.Cast()) + { + Regex regex = new Regex(regexPattern, RegexOptions.IgnoreCase); + Match match = regex.Match(item); + if (match.Success) + { + // Check for keep_before and keep_after groups in the match to retain surrounding text + if (match.Groups["keep_before"].Success && match.Groups["keep_after"].Success) + { + item = regex.Replace(item, $"{match.Groups["keep_before"].Value}{mask}{match.Groups["keep_after"].Value}"); + } + else if (match.Groups["keep_before"].Success) + { + item = regex.Replace(item, $"{match.Groups["keep_before"].Value}{mask}"); + } + else if (match.Groups["keep_after"].Success) + { + item = regex.Replace(item, $"{mask}{match.Groups["keep_after"].Value}"); + } + else + { + item = regex.Replace(item, mask); + } + } + } + return item; + } + } } diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index 273fcf85b..f7a7d47c5 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -38,7 +38,7 @@ function Get-PodeLoggingTerminalMethod { } # protect then write - $Item = ($Item | Protect-PodeLogItem) + $Item = ([pode.PodeLogger]::ProtectLogItem($Item, $PodeContext.Server.Logging.Masking)) #($Item | Protect-PodeLogItem) $Item.ToString() | Out-PodeHost } } @@ -117,30 +117,19 @@ function Get-PodeLoggingFileMethod { } # Mask values - $outString = ($Item | Protect-PodeLogItem).ToString() + $outString = ([pode.PodeLogger]::ProtectLogItem($Item, $PodeContext.Server.Logging.Masking)).ToString() } else { if ($RawItem -is [array]) { $tmpStrings = @() foreach ($ritem in $RawItem) { - if ($Options.Format -eq 'Simple') { - $tmpStrings += (ConvertTo-PodeSyslogFormat -RawItem $ritem -Options $Options) - } - else { - $tmpStrings += ConvertTo-PodeSyslogFormat -RawItem $ritem -RFC $Options.Format -Options $Options - } - + $tmpStrings += [pode.PodeFormat]::Syslog($RawItem, $Options, $PodeContext.Server.Logging.Masking) } $outString = $tmpStrings -join [System.Environment]::NewLine } else { - if ($Options.Format -eq 'Simple') { - $outString = ConvertTo-PodeSyslogFormat -RawItem $RawItem -Options $Options - } - else { - $outString = ConvertTo-PodeSyslogFormat -RawItem $RawItem -RFC $Options.Format -Options $Options - } + $outString = [pode.PodeFormat]::Syslog($RawItem, $Options, $PodeContext.Server.Logging.Masking) } } @@ -167,140 +156,6 @@ function Get-PodeLoggingFileMethod { } } -<# -.SYNOPSIS -Converts a log item to a formatted syslog message. - -.DESCRIPTION -The `ConvertTo-PodeSyslogFormat` function takes a log item in hashtable format and converts it into a syslog message, formatted according to the specified RFC standard (RFC3164, RFC5424, or a custom format). The function generates syslog-compliant messages based on severity and other options provided. - -.PARAMETER RawItem -A hashtable representing the log data, including fields like `Message`, `StackTrace`, `Level`, `Date`, and `Tag`. - -.PARAMETER RFC -Specifies the syslog message format. Supported values are `RFC3164`, `RFC5424`, and `None` for a custom format. The default is `None`. - -.PARAMETER Options -Additional options to customize the message format. The hashtable may include: -- `DefaultTag`: Default tag used if `RawItem` does not contain a `Tag`. -- `MaxLength`: Maximum allowed message length. -- `DataFormat`: Custom date format for timestamps. -- `Separator`: Separator used between fields in custom format. - -.EXAMPLE -PS> ConvertTo-PodeSyslogFormat -RawItem @{ Message = "System Error"; Level = "error"; Date = (Get-Date); Tag = "App" } -RFC 'RFC3164' -Options @{ DefaultTag = "MyApp"; MaxLength = 1024 } - -Converts a log item to an RFC3164-formatted syslog message with a maximum length of 1024 characters and a custom tag. - -.EXAMPLE -PS> ConvertTo-PodeSyslogFormat -RawItem @{ Message = "Connection established"; Level = "info"; Date = (Get-Date) } -Options @{ DefaultTag = "Service"; DataFormat = "yyyy-MM-dd HH:mm:ss"; Separator = " | " } - -Creates a custom-formatted log message with a user-defined timestamp format and separator. - -.NOTES -This function sets a severity level based on the `Level` field in `RawItem`. If the `RFC` parameter is set to `None`, the message will follow a custom format using the `Options` hashtable. -#> -function ConvertTo-PodeSyslogFormat { - param( - - [Parameter(Mandatory = $true )] - [hashtable] - $RawItem, - - [Parameter(Mandatory = $false )] - [ValidateSet('RFC3164', 'RFC5424', 'None')] - [string] - $RFC = 'None', - - [Parameter(Mandatory = $true )] - [hashtable] - $Options - ) - $MaxLength = -1 - # Mask values - if ($RawItem.Message) { - if ($RawItem.StackTrace) { - $message = "$($RawItem.Level.ToUpperInvariant()): $($RawItem.Message | Protect-PodeLogItem). Exception Type: $($RawItem.Category). Stack Trace: $($RawItem.StackTrace)" - } - else { - $message = ($RawItem.Message | Protect-PodeLogItem) - } - } - # Map $Level to syslog severity - switch ($RawItem.Level) { - 'emergency' { $severity = 0; break } - 'alert' { $severity = 1; break } - 'critical' { $severity = 2; break } - 'error' { $severity = 3; break } - 'warning' { $severity = 4; break } - 'notice' { $severity = 5; break } - 'info' { $severity = 6; break } - 'informational' { $severity = 6; break } - 'debug' { $severity = 7; break } - default { $severity = 6 } # Default to Informational - } - - # If the tag is not specified, use the default tag for the log method. - if ([string]::IsNullOrEmpty($RawItem.Tag)) { - $tag = $Options.DefaultTag - } - else { - $tag = $RawItem.Tag - } - - # Define the facility and severity - $facility = 1 # User-level messages - $priority = ($facility * 8) + $severity - - $processId = $PID - # Determine the syslog message format - switch ($RFC) { - 'RFC3164' { - # Set the max message length per RFC 3164 section 4.1 - $MaxLength = 1024 - # Assemble the full syslog formatted message - $timestamp = $RawItem.Date.ToString('MMM dd HH:mm:ss') - $fullSyslogMessage = "<$priority>$timestamp $($PodeContext.Server.ComputerName) $($tag): $message" - break - } - 'RFC5424' { - $timestamp = $RawItem.Date.ToString('yyyy-MM-ddTHH:mm:ss.ffffffK') - - # Assemble the full syslog formatted message - $fullSyslogMessage = "<$priority>1 $timestamp $($PodeContext.Server.ComputerName) $tag $processId - - $message" - - # Set the max message length per RFC 5424 section 6.1 - $MaxLength = 2048 - - break - } - # Simple version - default { - $MaxLength = $Options.MaxLength - $DataFormat = $Options.DataFormat - $Separator = $Options.Separator - if ($DataFormat) { - $timestamp = $RawItem.Date.ToString($DataFormat) - } - else { - $timestamp = $DataFormat - } - # Assemble the full syslog formatted message - $fullSyslogMessage = "$timestamp$Separator$($RawItem.Level)$Separator$($tag)$Separator$message" - # Set the max message length - if ($Options.MaxLength) { - $MaxLength = $Options.MaxLength - } - - } - } - # Ensure that the message is not too long - if ($MaxLength -gt 0 -and $fullSyslogMessage.Length -gt $MaxLength) { - return $fullSyslogMessage.Substring(0, $MaxLength) - } - # Return the full syslog formatted message - return $fullSyslogMessage -} <# .SYNOPSIS @@ -375,7 +230,8 @@ function Get-PodeLoggingSysLogMethod { } for ($i = 0; $i -lt $RawItem.Length; $i++) { - $fullSyslogMessage = ConvertTo-PodeSyslogFormat -RawItem $RawItem[$i] -RFC $Options.SyslogProtocol -Options $Options + $fullSyslogMessage = [pode.PodeFormat]::Syslog($RawItem[$i], $Options, $PodeContext.Server.Logging.Masking) + # Convert the message to a byte array $byteMessage = $($Options.Encoding).GetBytes($fullSyslogMessage) @@ -486,8 +342,8 @@ function Get-PodeLoggingEventViewerMethod { $entryLog.Source = $Options.Source try { - # Mask values and write the event to the Event Viewer - $message = ($Item[$i] | Protect-PodeLogItem) + # Mask values and write the event to the Event Viewer + $message = ([pode.PodeLogger]::ProtectLogItem($Item[$i], $PodeContext.Server.Logging.Masking)) $entryLog.WriteEvent($entryInstance, $message) } catch { @@ -813,16 +669,17 @@ Write-PodeRequestLog -Request $webEvent.Request -Response $webEvent.Response -Pa function Write-PodeRequestLog { param( [Parameter(Mandatory = $true)] + [PodeHttpRequest] $Request, [Parameter(Mandatory = $true)] + [PodeResponse] $Response, [Parameter()] [string] $Path ) - # Do nothing if logging is disabled, or request logging isn't set up $name = Get-PodeRequestLoggingName if (!(Test-PodeLoggerEnabled -Name $name)) { diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 6ac045c1b..5aaa58f02 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -1115,37 +1115,8 @@ function Protect-PodeLogItem { ) Process { - # do nothing if there are no masks - if (Test-PodeIsEmpty $PodeContext.Server.Logging.Masking.Patterns) { - return $item - } - - # attempt to apply each mask - foreach ($mask in $PodeContext.Server.Logging.Masking.Patterns) { - if ($Item -imatch $mask) { - # has both keep before/after - if ($Matches.ContainsKey('keep_before') -and $Matches.ContainsKey('keep_after')) { - $Item = ($Item -ireplace $mask, "`${keep_before}$($PodeContext.Server.Logging.Masking.Mask)`${keep_after}") - } - - # has just keep before - elseif ($Matches.ContainsKey('keep_before')) { - $Item = ($Item -ireplace $mask, "`${keep_before}$($PodeContext.Server.Logging.Masking.Mask)") - } - - # has just keep after - elseif ($Matches.ContainsKey('keep_after')) { - $Item = ($Item -ireplace $mask, "$($PodeContext.Server.Logging.Masking.Mask)`${keep_after}") - } - - # normal mask - else { - $Item = ($Item -ireplace $mask, $PodeContext.Server.Logging.Masking.Mask) - } - } - } - return $Item + return ([pode.PodeLogger]::ProtectLogItem($Item, $PodeContext.Server.Logging.Masking)) } } diff --git a/src/Public/LoggingMethod.ps1 b/src/Public/LoggingMethod.ps1 index 39cc08875..367954fae 100644 --- a/src/Public/LoggingMethod.ps1 +++ b/src/Public/LoggingMethod.ps1 @@ -687,7 +687,7 @@ function New-PodeSyslogLoggingMethod { Hostname = $Hostname TlsProtocols = $TlsProtocol SkipCertificateCheck = $SkipCertificateCheck.IsPresent - SyslogProtocol = $SyslogProtocol + Format = $SyslogProtocol Encoding = $selectedEncoding FailureAction = $FailureAction DataFormat = $DataFormat @@ -857,7 +857,7 @@ function New-PodeAwsLoggingMethod { # Format each log entry for CloudWatch Logs. $events = $Item | ForEach-Object { @{ - message = ($_ | Protect-PodeLogItem) # Sanitize log message content + message = ([pode.PodeLogger]::ProtectLogItem($_, $PodeContext.Server.Logging.Masking)) #($_ | Protect-PodeLogItem) # Sanitize log message content # Sanitize log message content timestamp = [math]::Round(($RawItem.Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalMilliseconds) # Timestamp in milliseconds since epoch } } @@ -1051,7 +1051,7 @@ function New-PodeAzureLoggingMethod { # Format each log entry for Azure Monitor. $records = $Item | ForEach-Object { @{ - message = ($_ | Protect-PodeLogItem) # Sanitize log message content + message = ([pode.PodeLogger]::ProtectLogItem($_, $PodeContext.Server.Logging.Masking)) #($_ | Protect-PodeLogItem) # Sanitize log message content # Sanitize log message content severity = $RawItem.Level.ToUpperInvariant() # Set log severity level timestamp = $RawItem.Date.ToString('yyyy-MM-ddTHH:mm:ss.fffZ') # Format timestamp in ISO 8601 tag = $RawItem.Tag # Include tag if provided @@ -1238,7 +1238,7 @@ function New-PodeGoogleLoggingMethod { # Format each log entry for Google Cloud Logging. $entries = $Item | ForEach-Object { @{ - textPayload = ($_ | Protect-PodeLogItem) # Sanitize log message content + textPayload = ([pode.PodeLogger]::ProtectLogItem($_, $PodeContext.Server.Logging.Masking)) #($_ | Protect-PodeLogItem) # Sanitize log message content # Sanitize log message content severity = $RawItem.Level.ToUpperInvariant() # Set log severity level timestamp = $RawItem.Date.ToString('yyyy-MM-ddTHH:mm:ss.fffZ') # Format timestamp in ISO 8601 labels = @{ @@ -1429,7 +1429,7 @@ function New-PodeDatadogLoggingMethod { # Format each log entry for Datadog. $events = $Item | ForEach-Object { @{ - message = ($_ | Protect-PodeLogItem) # Sanitize log message content + message = ([pode.PodeLogger]::ProtectLogItem($_, $PodeContext.Server.Logging.Masking)) #($_ | Protect-PodeLogItem) # Sanitize log message content # Sanitize log message content host = $PodeContext.Server.ComputerName # Add hostname service = $RawItem.Tag # Use tag as the service if provided date_happened = [math]::Round(($RawItem.Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalSeconds) # Convert timestamp to seconds since epoch @@ -1609,7 +1609,7 @@ function New-PodeElasticsearchLoggingMethod { # Format each log entry for Elasticsearch. $documents = $Item | ForEach-Object { @{ - message = ($_ | Protect-PodeLogItem) # Sanitize log message content + message = ([pode.PodeLogger]::ProtectLogItem($_, $PodeContext.Server.Logging.Masking)) #($_ | Protect-PodeLogItem) # Sanitize log message content # Sanitize log message content timestamp = $RawItem.Date.ToString('yyyy-MM-ddTHH:mm:ss.fffZ') # Format timestamp in ISO 8601 severity = $RawItem.Level.ToUpperInvariant() # Set log severity level host = $PodeContext.Server.ComputerName # Add hostname @@ -1783,7 +1783,7 @@ function New-PodeGraylogLoggingMethod { @{ version = '1.1' # GELF version host = $PodeContext.Server.ComputerName # Add hostname - short_message = ($_ | Protect-PodeLogItem) # Sanitize log message content + short_message = ([pode.PodeLogger]::ProtectLogItem($_, $PodeContext.Server.Logging.Masking)) #($_ | Protect-PodeLogItem) # Sanitize log message content # Sanitize log message content timestamp = [math]::Round(($RawItem.Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalSeconds) # Convert timestamp to seconds since epoch level = $RawItem.Level.ToUpperInvariant() # Set log severity level _tag = $RawItem.Tag # Include tag if provided @@ -1961,7 +1961,7 @@ function New-PodeSplunkLoggingMethod { # Format each log entry for Splunk. $events = $Item | ForEach-Object { @{ - event = ($_ | Protect-PodeLogItem) # Sanitize log message content + event = ([pode.PodeLogger]::ProtectLogItem($_, $PodeContext.Server.Logging.Masking)) #($_ | Protect-PodeLogItem) # Sanitize log message content # Sanitize log message content host = $PodeContext.Server.ComputerName # Add hostname source = $RawItem.Tag # Use tag as the source if provided time = [math]::Round(($RawItem.Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalSeconds) # Convert timestamp to seconds since epoch @@ -2138,7 +2138,7 @@ function New-PodeLogInsightLoggingMethod { # Process each log entry and format as required by Log Insight $messages = $Item | ForEach-Object { @{ - text = ($_ | Protect-PodeLogItem) # Sanitize the log message + text = ([pode.PodeLogger]::ProtectLogItem($_, $PodeContext.Server.Logging.Masking)) #($_ | Protect-PodeLogItem) # Sanitize log message content # Sanitize the log message timestamp = [math]::Round(($RawItem.Date).ToUniversalTime().Subtract(([datetime]::UnixEpoch)).TotalMilliseconds) # Convert date to milliseconds since epoch fields = @{ severity = $RawItem.Level.ToUpperInvariant() # Add severity level From a530fc51b250bae3abc8f067c380d3c85f831970 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 2 Nov 2024 10:19:05 -0700 Subject: [PATCH 32/53] remove get-PodeloggingName --- src/Listener/PodeFormat.cs | 53 ++++++++++++++------------ src/Listener/PodeLogger.cs | 29 ++++++++++++-- src/Private/Logging.ps1 | 74 +++++------------------------------- src/Public/Logging.ps1 | 20 +++++----- tests/unit/Logging.Tests.ps1 | 31 ++++++++++++--- 5 files changed, 97 insertions(+), 110 deletions(-) diff --git a/src/Listener/PodeFormat.cs b/src/Listener/PodeFormat.cs index 99e77dc6a..f363d880b 100644 --- a/src/Listener/PodeFormat.cs +++ b/src/Listener/PodeFormat.cs @@ -8,7 +8,7 @@ namespace Pode public static class PodeFormat { /// - /// Helper function to sanitize input by returning a default value if the input is null or whitespace. + /// Sanitizes input by returning a default value if the input is null or whitespace. /// /// The object value to be sanitized. /// A sanitized string, or "-" if the input is null or whitespace. @@ -18,37 +18,38 @@ private static string Sanitize(object value) } /// - /// Formats error log entries based on the provided options. Includes details like Date, Level, ThreadId, Server, Category, Message, and StackTrace. + /// Formats error log entries based on the provided options, including Date, Level, ThreadId, Server, Category, Message, and StackTrace. /// /// A hashtable containing log details. /// A hashtable containing format options such as Levels, Raw, and DataFormat. /// A formatted log string, or the original item if Raw is specified, or null if Level is not in options.Levels. public static object ErrorsLog(Hashtable item, Hashtable options) { - // Check if required keys are present and valid if (item == null || options == null) return null; + + // Check for required keys in the log item if (!item.ContainsKey("Level") || !item.ContainsKey("Date") || !item.ContainsKey("ThreadId") || !item.ContainsKey("Server") || !item.ContainsKey("Category") || !item.ContainsKey("Message") || !item.ContainsKey("StackTrace")) { return null; } - // Check if the error level is present in the options' Levels array + // Ensure the log level is present in the allowed levels if (options.ContainsKey("Levels") && !((IList)options["Levels"]).Contains(item["Level"])) { return null; } - // Return the raw item if Raw is set + // Return raw item if Raw option is set if (options.ContainsKey("Raw") && (bool)options["Raw"]) { return item; } - // Retrieve data format or default to a basic date format + // Set the date format or use a default string dataFormat = options.ContainsKey("DataFormat") ? options["DataFormat"].ToString() : "yyyy-MM-dd HH:mm:ss"; - // Construct the log entry with specified fields + // Build the log entry string StringBuilder sb = new StringBuilder(); return sb.Append("Date: ").Append(((DateTime)item["Date"]).ToString(dataFormat)).Append(" Level: ").Append(Sanitize(item["Level"])) .Append(" ThreadId: ").Append(Sanitize(item["ThreadId"])).Append(" Server: ").Append(Sanitize(item["Server"])).Append(" Category: ") @@ -57,7 +58,7 @@ public static object ErrorsLog(Hashtable item, Hashtable options) } /// - /// Formats request log entries based on the provided log format in options. Supports "extended", "common", "json", and "combined" formats. + /// Formats request log entries based on the specified format in options, supporting formats like "extended", "common", "json", and "combined". /// /// A hashtable containing request log details. /// A hashtable containing format options such as LogFormat and Raw. @@ -66,16 +67,15 @@ public static object RequestLog(Hashtable item, Hashtable options) { if (item == null || options == null) return null; - // Return the raw item if Raw is set + // Return raw item if Raw option is set if (options.ContainsKey("Raw") && (bool)options["Raw"]) { return item; } - // Retrieve log format or default to "combined" + // Retrieve the log format, defaulting to "combined" string logFormat = options.ContainsKey("LogFormat") ? options["LogFormat"].ToString().ToLowerInvariant() : "combined"; - // Construct the log entry based on the specified format StringBuilder sb = new StringBuilder(); switch (logFormat) @@ -137,7 +137,7 @@ public static object RequestLog(Hashtable item, Hashtable options) } /// - /// Formats general log entries, checking for level filtering and the presence of required fields. + /// Formats general log entries by checking for level filtering and required fields. /// /// A hashtable containing general log details. /// A hashtable containing format options such as Levels, Raw, and DataFormat. @@ -146,37 +146,41 @@ public static object GeneralLog(Hashtable item, Hashtable options) { if (item == null || options == null) return null; - // Check if the error level is present in the options' Levels array + // Ensure the log level is present in the allowed levels if (options.ContainsKey("Levels") && !((IList)options["Levels"]).Contains(item["Level"])) { return null; } - // Return the raw item if Raw is set + // Return raw item if Raw option is set if (options.ContainsKey("Raw") && (bool)options["Raw"]) { return item; } - // Retrieve data format or default to a basic date format + // Set the date format or use a default string dataFormat = options.ContainsKey("DataFormat") ? options["DataFormat"].ToString() : "yyyy-MM-dd HH:mm:ss"; - // Construct the log entry with specified fields + // Build the log entry string StringBuilder sb = new StringBuilder(); return sb.Append('[').Append(((DateTime)item["Date"]).ToString(dataFormat)).Append("] ") .Append(Sanitize(item["Level"])).Append(' ').Append(Sanitize(item["Tag"])).Append(' ').Append(Sanitize(item["ThreadId"])).Append(' ').Append(Sanitize(item["Message"])) .ToString(); } - - - + /// + /// Formats a syslog message from raw data and applies masking where necessary. + /// + /// A hashtable representing raw log data. + /// A hashtable containing options for message formatting, such as Format, DataFormat, and MaxLength. + /// A hashtable with masking patterns to obfuscate sensitive information in the message. + /// A formatted syslog message string. public static string Syslog(Hashtable rawItem, Hashtable options, Hashtable masking) { int maxLength = -1; string message = string.Empty; - // Process Message and StackTrace + // Process message and stack trace, applying masking if available if (rawItem.ContainsKey("Message")) { if (rawItem.ContainsKey("StackTrace") && !string.IsNullOrEmpty(rawItem["StackTrace"] as string)) @@ -189,7 +193,7 @@ public static string Syslog(Hashtable rawItem, Hashtable options, Hashtable mask } } - // Map Level to Syslog Severity + // Map log level to syslog severity int severity; string level = rawItem["Level"].ToString().ToLowerInvariant(); switch (level) @@ -203,22 +207,21 @@ public static string Syslog(Hashtable rawItem, Hashtable options, Hashtable mask case "info": case "informational": severity = 6; break; case "debug": severity = 7; break; - default: severity = 6; break; // Default to Informational + default: severity = 6; break; } - // Define Tag + // Set tag and priority string tag = string.IsNullOrEmpty(rawItem["Tag"] as string) ? (options != null && options.ContainsKey("DefaultTag") ? options["DefaultTag"].ToString() : "DefaultTag") : rawItem["Tag"].ToString(); - // Define Facility and Priority int facility = 1; // User-level messages int priority = (facility * 8) + severity; int processId = System.Diagnostics.Process.GetCurrentProcess().Id; string fullSyslogMessage; string timestamp; - // Determine Syslog Message Format + // Determine syslog format and format accordingly switch (options["Format"].ToString().ToUpper()) { case "RFC3164": diff --git a/src/Listener/PodeLogger.cs b/src/Listener/PodeLogger.cs index 4dd44f1c9..e91ba5a9a 100644 --- a/src/Listener/PodeLogger.cs +++ b/src/Listener/PodeLogger.cs @@ -8,6 +8,27 @@ namespace Pode { public static class PodeLogger { + /// + /// The name used for the request logger. + /// + public const string RequestLogName = "__pode_log_requests__"; + + /// + /// The name used for the default logger. + /// + public const string DefaultLogName = "__pode_log_Defaults__"; + + /// + /// The name used for the error logger. + /// + public const string ErrorLogName = "__pode_log_errors__"; + + /// + /// The name used for the error logger. + /// + public const string ListenerLogName = "__pode_log_Listener__"; + + // Static fields to control logging and store log entries in a thread-safe queue private static bool _enabled; private static ConcurrentQueue _queue; @@ -132,7 +153,7 @@ public static void Clear() { Hashtable logEntry = new Hashtable { - ["Name"] = "Listener", + ["Name"] = ListenerLogName, ["Item"] = ex }; @@ -179,7 +200,7 @@ public static void Clear() { Hashtable logEntry = new Hashtable { - ["Name"] = "Listener", + ["Name"] = ListenerLogName, ["Item"] = new Hashtable { ["Message"] = message, @@ -206,8 +227,8 @@ public static void Clear() /// The masked log item as a string. public static string ProtectLogItem(string item, Hashtable masking) { - // Exit if there are no masking patterns - if (masking == null || masking.Count == 0) + // Exit if there are no masking patterns or if the mask value is null, empty, or missing + if (masking == null || masking.Count == 0 || !masking.ContainsKey("Mask") || string.IsNullOrEmpty(masking["Mask"]?.ToString())) { return item; } diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index f7a7d47c5..56f81f2ed 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -342,7 +342,7 @@ function Get-PodeLoggingEventViewerMethod { $entryLog.Source = $Options.Source try { - # Mask values and write the event to the Event Viewer + # Mask values and write the event to the Event Viewer $message = ([pode.PodeLogger]::ProtectLogItem($Item[$i], $PodeContext.Server.Logging.Masking)) $entryLog.WriteEvent($entryInstance, $message) } @@ -451,62 +451,6 @@ function Get-PodeLoggingInbuiltType { return $script } -<# -.SYNOPSIS -Gets the name of the request logger. - -.DESCRIPTION -This function returns the name of the logger used for logging requests in Pode. - -.RETURNS -[string] - The name of the request logger. - -.EXAMPLE -Get-PodeRequestLoggingName -#> -function Get-PodeRequestLoggingName { - # Return the name of the request logger - return '__pode_log_requests__' -} - - -<# -.SYNOPSIS -Gets the name of the error logger. - -.DESCRIPTION -This function returns the name of the logger used for logging errors in Pode. - -.RETURNS -[string] - The name of the error logger. - -.EXAMPLE -Get-PodeErrorLoggingName -#> -function Get-PodeErrorLoggingName { - # Return the name of the error logger - return '__pode_log_errors__' -} - - -<# -.SYNOPSIS -Gets the name of the Default logger. - -.DESCRIPTION -This function returns the name of the logger used for logging Defaults in Pode. - -.RETURNS -[string] - The name of the Default logger. - -.EXAMPLE -Get-PodeDefaultLoggingName -#> -function Get-PodeDefaultLoggingName { - # Return the name of the Default logger - return '__pode_log_Defaults__' -} - <# .SYNOPSIS Retrieves a Pode logger by name. @@ -613,7 +557,7 @@ function Test-PodeLoggerEnabled { This is an internal function and may change in future releases of Pode. #> function Get-PodeErrorLoggingLevel { - return (Get-PodeLogger -Name (Get-PodeErrorLoggingName)).Arguments.Levels + return (Get-PodeLogger -Name ([Pode.PodeLogger]::ErrorLogName)).Arguments.Levels } <# @@ -628,7 +572,7 @@ Test-PodeErrorLoggingEnabled #> function Test-PodeErrorLoggingEnabled { # Get the name of the error logger and test if it is enabled - return (Test-PodeLoggerEnabled -Name (Get-PodeErrorLoggingName)) + return (Test-PodeLoggerEnabled -Name ([Pode.PodeLogger]::ErrorLogName)) } <# @@ -643,7 +587,7 @@ Test-PodeRequestLoggingEnabled #> function Test-PodeRequestLoggingEnabled { # Get the name of the request logger and test if it is enabled - return (Test-PodeLoggerEnabled -Name (Get-PodeRequestLoggingName)) + return (Test-PodeLoggerEnabled -Name ([Pode.PodeLogger]::RequestLogName)) } @@ -681,7 +625,7 @@ function Write-PodeRequestLog { $Path ) # Do nothing if logging is disabled, or request logging isn't set up - $name = Get-PodeRequestLoggingName + $name = [Pode.PodeLogger]::RequestLogName if (!(Test-PodeLoggerEnabled -Name $name)) { return } @@ -764,7 +708,7 @@ function Add-PodeRequestLogEndware { ) # Do nothing if logging is disabled, or request logging isn't set up - $name = Get-PodeRequestLoggingName + $name = [Pode.PodeLogger]::RequestLogName if (!(Test-PodeLoggerEnabled -Name $name)) { return } @@ -833,7 +777,7 @@ function Start-PodeLoggerDispatcher { Start-Sleep -Milliseconds 100 continue } - if ($log.Name -eq 'Listener') { + if ($log.Name -eq [pode.PodeLogger]::ListenerLogName) { if ($log.Item -is [System.Exception]) { Write-PodeErrorLog -Exception $log.Item -Level 'Error' -ThreadId $log.Item.ThreadId @@ -1092,11 +1036,11 @@ function Enable-PodeLoggingInternal { switch ($Type.ToLowerInvariant()) { 'errors' { - $name = Get-PodeErrorLoggingName + $name = [Pode.PodeLogger]::ErrorLogName $scriptBlock = (Get-PodeLoggingInbuiltType -Type Errors) } 'Default' { - $name = Get-PodeDefaultLoggingName + $name = [Pode.PodeLogger]::DefaultLogName $scriptBlock = (Get-PodeLoggingInbuiltType -Type Default) } } diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 5aaa58f02..8192bd263 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -263,7 +263,7 @@ function Enable-PodeRequestLogging { Test-PodeIsServerless -FunctionName 'Enable-PodeRequestLogging' -ThrowError - $name = Get-PodeRequestLoggingName + $name = [Pode.PodeLogger]::RequestLogName # error if it's already enabled if ($PodeContext.Server.Logging.Type.Contains($name)) { @@ -537,7 +537,7 @@ function Disable-PodeRequestLogging { [CmdletBinding()] param() - Remove-PodeLogger -Name (Get-PodeRequestLoggingName) + Remove-PodeLogger -Name ([Pode.PodeLogger]::RequestLogName) } <# @@ -578,7 +578,7 @@ function Disable-PodeErrorLogging { [CmdletBinding()] param() - Remove-PodeLogger -Name (Get-PodeErrorLoggingName) + Remove-PodeLogger -Name ([Pode.PodeLogger]::ErrorLogName) } @@ -597,7 +597,7 @@ function Disable-PodeDefaultLogging { [CmdletBinding()] param() - Remove-PodeLogger -Name (Get-PodeDefaultLoggingName) + Remove-PodeLogger -Name ([Pode.PodeLogger]::DefaultLogName) } @@ -842,10 +842,10 @@ function Write-PodeErrorLog { ) Process { - $name = Get-PodeErrorLoggingName + $name = [Pode.PodeLogger]::ErrorLogName Write-PodeLog @PSBoundParameters -name $name -SuppressErrorLog if ($PodeContext.Server.Logging.Type[$name].DuplicateToDefaultLog -and ! $SuppressDefaultLog) { - Write-PodeLog @PSBoundParameters -name (Get-PodeDefaultLoggingName) -SuppressErrorLog + Write-PodeLog @PSBoundParameters -name ([Pode.PodeLogger]::DefaultLogName) -SuppressErrorLog } } } @@ -962,7 +962,7 @@ function Write-PodeLog { ) begin { if (!$Name) { - $Name = Get-PodeDefaultLoggingName + $Name = [Pode.PodeLogger]::DefaultLogName } # Get the configured log method. @@ -1040,7 +1040,7 @@ function Write-PodeLog { if ((! $SuppressErrorLog.IsPresent) -and (Test-PodeErrorLoggingEnabled)) { if ($PSCmdlet.ParameterSetName.ToLowerInvariant() -eq 'exception') { [Pode.PodeLogger]::Enqueue( @{ - Name = Get-PodeErrorLoggingName + Name = [Pode.PodeLogger]::ErrorLogName Item = @{ Server = $logItem.Item.Server Level = $Level @@ -1056,7 +1056,7 @@ function Write-PodeLog { } elseif ($PSCmdlet.ParameterSetName.ToLowerInvariant() -eq 'errorrecord') { [Pode.PodeLogger]::Enqueue( @{ - Name = Get-PodeErrorLoggingName + Name = [Pode.PodeLogger]::ErrorLogName Item = @{ Server = $logItem.Item.Server Level = $Level @@ -1071,7 +1071,7 @@ function Write-PodeLog { } elseif ($Level -eq 'Error') { [Pode.PodeLogger]::Enqueue( @{ - Name = Get-PodeErrorLoggingName + Name = [Pode.PodeLogger]::ErrorLogName Item = @{ Server = $logItem.Item.Server Level = $Level diff --git a/tests/unit/Logging.Tests.ps1 b/tests/unit/Logging.Tests.ps1 index 9d5e30c69..e31112462 100644 --- a/tests/unit/Logging.Tests.ps1 +++ b/tests/unit/Logging.Tests.ps1 @@ -6,7 +6,26 @@ BeforeAll { Get-ChildItem "$($src)/*.ps1" -Recurse | Resolve-Path | ForEach-Object { . $_ } Import-LocalizedData -BindingVariable PodeLocale -BaseDirectory (Join-Path -Path $src -ChildPath 'Locales') -FileName 'Pode' if (!([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq 'Pode' })) { - Add-Type -LiteralPath "$($src)/Libs/netstandard2.0/Pode.dll" -ErrorAction Stop + + # fetch the .net version and the libs path + $version = [System.Environment]::Version.Major + $libsPath = Join-Path -Path $src -ChildPath 'Libs' + + # filter .net dll folders based on version above, and get path for latest version found + if (![string]::IsNullOrWhiteSpace($version)) { + $netFolder = Get-ChildItem -Path $libsPath -Directory -Force | + Where-Object { $_.Name -imatch "net[1-$($version)]" } | + Sort-Object -Property Name -Descending | + Select-Object -First 1 -ExpandProperty FullName + } + + # use netstandard if no folder found + if ([string]::IsNullOrWhiteSpace($netFolder)) { + $netFolder = "$($libsPath)/netstandard2.0" + } + + # append Pode.dll and mount + Add-Type -LiteralPath "$($netFolder)/Pode.dll" -ErrorAction Stop } [Pode.PodeLogger]::Enabled = $true @@ -58,7 +77,7 @@ Describe 'Write-PodeLog' { It 'Adds a log item' { Mock Test-PodeLoggerEnabled { return $true } - Mock Get-PodeLoggingLevel {return @('Informational')} + Mock Get-PodeLoggingLevel { return @('Informational') } Write-PodeLog -Name 'test' -InputObject 'test' [Pode.PodeLogger]::Count | Should -Be 1 @@ -138,15 +157,15 @@ Describe 'Write-PodeErrorLog' { } } -Describe 'Get-PodeRequestLoggingName' { +Describe '[Pode.PodeLogger]::RequestLogName' { It 'Returns logger name' { - Get-PodeRequestLoggingName | Should -Be '__pode_log_requests__' + [Pode.PodeLogger]::RequestLogName | Should -Be '__pode_log_requests__' } } -Describe 'Get-PodeErrorLoggingName' { +Describe '[Pode.PodeLogger]::ErrorLogName' { It 'Returns logger name' { - Get-PodeErrorLoggingName | Should -Be '__pode_log_errors__' + [Pode.PodeLogger]::ErrorLogName | Should -Be '__pode_log_errors__' } } From 6c9aa8016b36b64f83af1f2b1699431bc2e2e4ef Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 2 Nov 2024 14:08:59 -0700 Subject: [PATCH 33/53] fix merge --- src/Listener/PodeContext.cs | 14 +++----------- src/Listener/PodeLogger.cs | 6 +++--- src/Listener/PodeRequest.cs | 2 +- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/Listener/PodeContext.cs b/src/Listener/PodeContext.cs index 2fe83b5d3..be1002c12 100644 --- a/src/Listener/PodeContext.cs +++ b/src/Listener/PodeContext.cs @@ -189,21 +189,13 @@ private async Task NewRequest() try { await Request.Open(CancellationToken.None).ConfigureAwait(false); - State = PodeContextState.Open; - } - catch (AggregateException aex) - { - PodeHelpers.HandleAggregateException(aex, Listener, PodeLoggingLevel.Debug, true); - State = Request.InputStream == default(Stream) - ? PodeContextState.Error - : PodeContextState.SslError; + State = Request.State == PodeStreamState.Open + ? PodeContextState.Open + : PodeContextState.Error; } catch (Exception ex) { PodeLogger.LogException(ex, Listener, PodeLoggingLevel.Debug); - State = Request.InputStream == default(Stream) - ? PodeContextState.Error - : PodeContextState.SslError; } // If the request is SMTP or TCP, send acknowledgment if available. diff --git a/src/Listener/PodeLogger.cs b/src/Listener/PodeLogger.cs index e91ba5a9a..c2aa96d34 100644 --- a/src/Listener/PodeLogger.cs +++ b/src/Listener/PodeLogger.cs @@ -130,7 +130,7 @@ public static void Clear() } // Exit if logging is disabled or the logging level isn’t configured in the connector - if (connector != null && (!connector.ErrorLoggingEnabled || !connector.ErrorLoggingLevels.Contains(level.ToString(), StringComparer.InvariantCultureIgnoreCase))) + if (connector != default(PodeConnector) && (!connector.ErrorLoggingEnabled || !connector.ErrorLoggingLevels.Contains(level.ToString(), StringComparer.InvariantCultureIgnoreCase))) { return; } @@ -177,7 +177,7 @@ public static void Clear() } // Exit if logging is disabled or the level isn’t configured in the connector - if (connector != null && (!connector.ErrorLoggingEnabled || !connector.ErrorLoggingLevels.Contains(level.ToString(), StringComparer.InvariantCultureIgnoreCase))) + if (connector != default(PodeConnector) && (!connector.ErrorLoggingEnabled || !connector.ErrorLoggingLevels.Contains(level.ToString(), StringComparer.InvariantCultureIgnoreCase))) { return; } @@ -185,7 +185,7 @@ public static void Clear() // If Terminal logging is enabled, output message to the console, including context ID if provided if (Terminal) { - if (context == null) + if (context == default(PodeContext)) { Console.WriteLine($"[{level}]: {message}"); } diff --git a/src/Listener/PodeRequest.cs b/src/Listener/PodeRequest.cs index 00408420b..13f6519d6 100644 --- a/src/Listener/PodeRequest.cs +++ b/src/Listener/PodeRequest.cs @@ -137,7 +137,7 @@ public async Task Open(CancellationToken cancellationToken) } else { - PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Debug); + PodeLogger.LogException(ex, Context.Listener, PodeLoggingLevel.Debug); } State = PodeStreamState.Error; From 6d9e798bd8e2369688a6be81492f84b239faa027 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Fri, 22 Nov 2024 06:19:56 -0800 Subject: [PATCH 34/53] Update pode.build.ps1 --- pode.build.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/pode.build.ps1 b/pode.build.ps1 index d82731d5c..d195004e7 100644 --- a/pode.build.ps1 +++ b/pode.build.ps1 @@ -113,6 +113,7 @@ function Invoke-PodeBuildDotnetBuild($target) { # Determine if the target framework is compatible $isCompatible = $False switch ($majorVersion) { + 9 { if ($target -in @('net6.0', 'netstandard2.0', 'net8.0', 'net9.0')) { $isCompatible = $True } } 8 { if ($target -in @('net6.0', 'netstandard2.0', 'net8.0')) { $isCompatible = $True } } 7 { if ($target -in @('net6.0', 'netstandard2.0')) { $isCompatible = $True } } 6 { if ($target -in @('net6.0', 'netstandard2.0')) { $isCompatible = $True } } From 1dde713e2b46e4438978ff85ad97d4c65adea4de Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sun, 26 Jan 2025 11:57:24 -0800 Subject: [PATCH 35/53] Update Pode.psd1 --- src/Locales/ja/Pode.psd1 | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/src/Locales/ja/Pode.psd1 b/src/Locales/ja/Pode.psd1 index 4c04f1d1f..535c707aa 100644 --- a/src/Locales/ja/Pode.psd1 +++ b/src/Locales/ja/Pode.psd1 @@ -292,7 +292,6 @@ fnDoesNotAcceptArrayAsPipelineInputExceptionMessage = "関数 '{0}' は配列をパイプライン入力として受け付けません。" unsupportedStreamCompressionEncodingExceptionMessage = 'サポートされていないストリーム圧縮エンコーディングが提供されました: {0}' localEndpointConflictExceptionMessage = "'{0}' と '{1}' は OpenAPI のローカルエンドポイントとして定義されていますが、API 定義ごとに 1 つのローカルエンドポイントのみ許可されます。" - localEndpointConflictExceptionMessage = "'{0}' と '{1}' は OpenAPI のローカルエンドポイントとして定義されていますが、API 定義ごとに 1 つのローカルエンドポイントのみ許可されます。" deprecatedFunctionWarningMessage = "警告: 関数 '{0}' は廃止され、将来のリリースで削除されます。代わりに '{1}' 関数を使用してください。" suspendingMessage = '停止' resumingMessage = '再開' @@ -324,33 +323,4 @@ showOpenAPIMessage = 'OpenAPIを表示' enableQuietModeMessage = 'クワイエットモードを有効化' disableQuietModeMessage = 'クワイエットモードを無効化' - resumingMessage = '再開' - serverControlCommandsTitle = 'サーバーコントロールコマンド:' - gracefullyTerminateMessage = 'サーバーを正常に終了します。' - restartServerMessage = 'サーバーを再起動して設定をリロードします。' - resumeServerMessage = 'サーバーを再開します。' - suspendServerMessage = 'サーバーを一時停止します。' - startingMessage = '開始中' - restartingMessage = '再起動中' - suspendedMessage = '一時停止中' - runningMessage = '実行中' - openHttpEndpointMessage = 'デフォルトのブラウザで最初の HTTP エンドポイントを開きます。' - terminatedMessage = '終了しました' - showMetricsMessage = 'メトリクスを表示' - clearConsoleMessage = 'コンソールをクリア' - serverMetricsMessage = 'サーバーメトリクス' - totalUptimeMessage = '総稼働時間:' - uptimeSinceLastRestartMessage = '最後の再起動からの稼働時間:' - totalRestartMessage = '再起動の総数:' - defaultEndpointAlreadySetExceptionMessage = "タイプ '{0}' のデフォルトエンドポイントは既に設定されています。タイプごとに1つのデフォルトエンドポイントのみ許可されています。" - enableHttpServerMessage = 'HTTPサーバーを有効化する' - disableHttpServerMessage = 'HTTPサーバーを無効化する' - showHelpMessage = 'ヘルプを表示' - hideHelpMessage = 'ヘルプを非表示' - hideEndpointsMessage = 'エンドポイントを非表示' - showEndpointsMessage = 'エンドポイントを表示' - hideOpenAPIMessage = 'OpenAPIを非表示' - showOpenAPIMessage = 'OpenAPIを表示' - enableQuietModeMessage = 'クワイエットモードを有効化' - disableQuietModeMessage = 'クワイエットモードを無効化' } \ No newline at end of file From f047cb641bf8f66054200c3b99a49adf5f925241 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Tue, 4 Feb 2025 08:12:42 -0800 Subject: [PATCH 36/53] Update Logging.ps1 --- src/Private/Logging.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index 19927cb51..ae0fc83f9 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -26,7 +26,7 @@ function Get-PodeLoggingTerminalMethod { } $log = @{} - while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + while (!(Test-PodeCancellationTokenRequest -Type Terminate)) { Start-Sleep -Milliseconds 100 if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { @@ -67,7 +67,7 @@ function Get-PodeLoggingFileMethod { param($MethodId) $log = @{} - while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + while (!(Test-PodeCancellationTokenRequest -Type Terminate)) { Start-Sleep -Milliseconds 100 if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { @@ -175,7 +175,7 @@ function Get-PodeLoggingSysLogMethod { $log = @{} $socketCreated = $false try { - while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + while (!(Test-PodeCancellationTokenRequest -Type Terminate)) { Start-Sleep -Milliseconds 100 if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { @@ -311,7 +311,7 @@ function Get-PodeLoggingEventViewerMethod { param($MethodId) $log = @{} - while (!$PodeContext.Tokens.Cancellation.IsCancellationRequested) { + while (!(Test-PodeCancellationTokenRequest -Type Terminate)) { Start-Sleep -Milliseconds 100 if ($PodeContext.Server.Logging.Method[$MethodId].Queue.TryDequeue([ref]$log)) { From bf2589911dc7d205160a49c7d9a5d4238cb5a0e3 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Tue, 4 Feb 2025 09:10:47 -0800 Subject: [PATCH 37/53] fixes post merge --- src/Private/CancellationToken.ps1 | 24 +--- src/Private/Context.ps1 | 5 +- src/Private/Helpers.ps1 | 18 +-- src/Private/Logging.ps1 | 175 +++++++++++++++--------------- 4 files changed, 104 insertions(+), 118 deletions(-) diff --git a/src/Private/CancellationToken.ps1 b/src/Private/CancellationToken.ps1 index 13fbd140d..22db5507d 100644 --- a/src/Private/CancellationToken.ps1 +++ b/src/Private/CancellationToken.ps1 @@ -340,7 +340,7 @@ function Wait-PodeCancellationTokenRequest { # Wait for the token to be reset, with exponential back-off $count = 1 - while ($PodeContext.Tokens[$Type].IsCancellationRequested) { + while (! ($PodeContext.Tokens[$Type].IsCancellationRequested)) { Start-Sleep -Milliseconds (100 * $count) $count = [System.Math]::Min($count + 1, 20) } @@ -366,9 +366,6 @@ function Wait-PodeCancellationTokenRequest { - `Start` - `Disable` -.PARAMETER Wait - If specified, waits until the token's cancellation request becomes active before returning the result. - .OUTPUTS [bool] Returns `$true` if the specified token has an active cancellation request, otherwise `$false`. @@ -377,11 +374,6 @@ function Wait-PodeCancellationTokenRequest { Checks if the Restart token has an active cancellation request and returns `$true` or `$false`. -.EXAMPLE - Test-PodeCancellationTokenRequest -Type 'Suspend' -Wait - - Waits until the Suspend token has an active cancellation request before returning `$true` or `$false`. - .NOTES This function is an internal utility for Pode and may be subject to change in future releases. #> @@ -390,21 +382,11 @@ function Test-PodeCancellationTokenRequest { [Parameter(Mandatory = $true)] [ValidateSet('Cancellation', 'Restart', 'Suspend', 'Resume', 'Terminate', 'Start', 'Disable')] [string] - $Type, - - [switch] - $Wait + $Type ) # Check if the specified token has an active cancellation request - $cancelled = $PodeContext.Tokens[$Type].IsCancellationRequested - - # If -Wait is specified, block until the token's cancellation request becomes active - if ($Wait) { - Wait-PodeCancellationTokenRequest -Type $Type - } - - return $cancelled + return $PodeContext.Tokens[$Type].IsCancellationRequested } diff --git a/src/Private/Context.ps1 b/src/Private/Context.ps1 index ad64d423a..d2c409f34 100644 --- a/src/Private/Context.ps1 +++ b/src/Private/Context.ps1 @@ -659,8 +659,9 @@ function New-PodeRunspacePool { # logs runspace - any log is running here $PodeContext.RunspacePools.Logs = @{ - Pool = [runspacefactory]::CreateRunspacePool(1, 1, $PodeContext.RunspaceState, $Host) - State = 'Waiting' + Pool = [runspacefactory]::CreateRunspacePool(1, 1, $PodeContext.RunspaceState, $Host) + State = 'Waiting' + LastId = 0 } # web runspace - if we have any http/s endpoints diff --git a/src/Private/Helpers.ps1 b/src/Private/Helpers.ps1 index 8c94de8af..e8090f3a8 100644 --- a/src/Private/Helpers.ps1 +++ b/src/Private/Helpers.ps1 @@ -555,8 +555,9 @@ function Close-PodeServerInternal { # PodeContext doesn't exist return if ($null -eq $PodeContext) { return } try { - #Disable Logging before closing + #Disable Logging before closing Disable-PodeLog + # ensure the token is cancelled Write-Verbose 'Cancelling main cancellation token' Close-PodeCancellationTokenRequest -Type Cancellation, Terminate @@ -583,12 +584,15 @@ function Close-PodeServerInternal { $_ | Out-Default } - # remove all of the pode temp drives - Write-Verbose 'Removing internal PSDrives' - Remove-PodePSDrive - - if ($ShowDoneMessage -and ($PodeContext.Server.Types.Length -gt 0) -and !$PodeContext.Server.IsServerless) { - Write-PodeHost $PodeLocale.doneMessage -ForegroundColor Green + # remove all of the pode temp drives + Write-Verbose 'Removing internal PSDrives' + Remove-PodePSDrive + } + finally { + if ($null -ne $PodeContext) { + # Remove any tokens + $PodeContext.Tokens = $null + } } } diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index ae0fc83f9..1936afcb1 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -760,111 +760,110 @@ function Start-PodeLoggerDispatcher { $scriptBlock = { $log = @{} - # Wait for the server to start before processing logs - if ( Wait-PodeServerToStart) { - try { - while (!(Test-PodeCancellationTokenRequest -Type Terminate)) { - # Check for suspension token and wait for the debugger to reset if active - Test-PodeSuspensionToken - try { - # Check if the log queue has reached its limit - if ([Pode.PodeLogger]::Count -ge $PodeContext.Server.Logging.QueueLimit) { - Invoke-PodeHandleFailure -Message "Reached the log Queue Limit of $($PodeContext.Server.Logging.QueueLimit)" -FailureAction $logger.Method.Arguments.FailureAction - } + # Waits for the Pode server to fully start before proceeding with further operations. + Wait-PodeCancellationTokenRequest -Type Start + try { + while (!(Test-PodeCancellationTokenRequest -Type Terminate)) { - # Try to dequeue a log entry from the queue - if ( [Pode.PodeLogger]::TryDequeue([ref]$log)) { - # If the log is null, check batch then sleep and skip - if ($null -eq $log) { - Start-Sleep -Milliseconds 100 - continue - } - if ($log.Name -eq [pode.PodeLogger]::ListenerLogName) { + try { + # Check if the log queue has reached its limit + if ([Pode.PodeLogger]::Count -ge $PodeContext.Server.Logging.QueueLimit) { + Invoke-PodeHandleFailure -Message "Reached the log Queue Limit of $($PodeContext.Server.Logging.QueueLimit)" -FailureAction $logger.Method.Arguments.FailureAction + } - if ($log.Item -is [System.Exception]) { - Write-PodeErrorLog -Exception $log.Item -Level 'Error' -ThreadId $log.Item.ThreadId - } - else { - Write-PodeLog -Message $log.Item.Message -ThreadId $log.Item.ThreadId -Tag 'Listener' -Level $log.Item.Level + # Try to dequeue a log entry from the queue + if ( [Pode.PodeLogger]::TryDequeue([ref]$log)) { + # If the log is null, check batch then sleep and skip + if ($null -eq $log) { + Start-Sleep -Milliseconds 100 + continue + } + if ($log.Name -eq [pode.PodeLogger]::ListenerLogName) { - } - continue + if ($log.Item -is [System.Exception]) { + Write-PodeErrorLog -Exception $log.Item -Level 'Error' -ThreadId $log.Item.ThreadId } + else { + Write-PodeLog -Message $log.Item.Message -ThreadId $log.Item.ThreadId -Tag 'Listener' -Level $log.Item.Level - # Run the log item through the appropriate method - $logger = $PodeContext.Server.Logging.Type[$log.Name] - $now = [datetime]::Now - - # Convert the log item into a writable format - $rawItem = $log.Item - $_args = @($log.Item) + @($logger.Arguments) - - $item = @(Invoke-PodeScriptBlock -ScriptBlock $logger.ScriptBlock -Arguments $_args -UsingVariables $logger.UsingVariables -Return -Splat) - - # Check batching - $batch = $logger.Method.Batch - if ($batch.Size -gt 1) { - # Add current item to batch - $batch.Items += $item - $batch.RawItems += $log.Item - $batch.LastUpdate = $now - - # If the current amount of items matches the batch size, write - $item = $null - if ($batch.Items.Length -ge $batch.Size) { - $item = $batch.Items - $rawItem = $batch.RawItems - } + } + continue + } - # If we're writing, reset the items - if ($null -ne $item) { - $batch.Items = @() - $batch.RawItems = @() - } + # Run the log item through the appropriate method + $logger = $PodeContext.Server.Logging.Type[$log.Name] + $now = [datetime]::Now + + # Convert the log item into a writable format + $rawItem = $log.Item + $_args = @($log.Item) + @($logger.Arguments) + + $item = @(Invoke-PodeScriptBlock -ScriptBlock $logger.ScriptBlock -Arguments $_args -UsingVariables $logger.UsingVariables -Return -Splat) + + # Check batching + $batch = $logger.Method.Batch + if ($batch.Size -gt 1) { + # Add current item to batch + $batch.Items += $item + $batch.RawItems += $log.Item + $batch.LastUpdate = $now + + # If the current amount of items matches the batch size, write + $item = $null + if ($batch.Items.Length -ge $batch.Size) { + $item = $batch.Items + $rawItem = $batch.RawItems } - # Send the writable log item off to the log writer + # If we're writing, reset the items if ($null -ne $item) { - foreach ($method in $logger.Method) { - if ($method.NoRunspace) { - # Legacy for custom methods - # $null = Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Logging.Method[$method.Id].ScriptBlock -Arguments $_args -UsingVariables $method.UsingVariables -Splat - $_args = @(, $item) + @($method.Arguments) + @(, $rawItem) - $null = Invoke-PodeScriptBlock -ScriptBlock $logger.Method.ScriptBlock -Arguments $_args -UsingVariables $logger.Method.UsingVariables -Splat - } - else { - $_args = @{ - Item = $item - Options = $method.Arguments - RawItem = $rawItem - } - $PodeContext.Server.Logging.Method[$method.Id].Queue.Enqueue($_args) + $batch.Items = @() + $batch.RawItems = @() + } + } + + # Send the writable log item off to the log writer + if ($null -ne $item) { + foreach ($method in $logger.Method) { + if ($method.NoRunspace) { + # Legacy for custom methods + # $null = Invoke-PodeScriptBlock -ScriptBlock $PodeContext.Server.Logging.Method[$method.Id].ScriptBlock -Arguments $_args -UsingVariables $method.UsingVariables -Splat + $_args = @(, $item) + @($method.Arguments) + @(, $rawItem) + $null = Invoke-PodeScriptBlock -ScriptBlock $logger.Method.ScriptBlock -Arguments $_args -UsingVariables $logger.Method.UsingVariables -Splat + } + else { + $_args = @{ + Item = $item + Options = $method.Arguments + RawItem = $rawItem } + $PodeContext.Server.Logging.Method[$method.Id].Queue.Enqueue($_args) } } - - # Small sleep to lower CPU usage - Start-Sleep -Milliseconds 100 - } - else { - # Check the logger batch - Test-PodeLoggerBatch - Start-Sleep -Seconds 5 } + + # Small sleep to lower CPU usage + Start-Sleep -Milliseconds 100 } - catch { - $_ | Write-PodeErrorLog + else { + # Check the logger batch + Test-PodeLoggerBatch + Start-Sleep -Seconds 5 } } + catch { + $_ | Write-PodeErrorLog + } } - catch [System.OperationCanceledException] { - $_ | Write-PodeErrorLog -Level Debug - } - catch { - $_ | Write-PodeErrorLog - throw $_.Exception - } } + catch [System.OperationCanceledException] { + $_ | Write-PodeErrorLog -Level Debug + } + catch { + $_ | Write-PodeErrorLog + throw $_.Exception + } + } # Retrieve unique method IDs @@ -875,7 +874,7 @@ function Start-PodeLoggerDispatcher { foreach ($methodId in $uniqueMethodIds) { if ($null -ne $PodeContext.Server.Logging.Method[$methodId]) { $PodeContext.Server.Logging.Method[$methodId].Queue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new() - $PodeContext.Server.Logging.Method[$methodId].Runspace = Add-PodeRunspace -PassThru -Type Logs -ScriptBlock $PodeContext.Server.Logging.Method[$methodId].ScriptBlock -Parameters @{ MethodId = $methodId } -Name 'Method' -Id $methodId | Out-Null + $PodeContext.Server.Logging.Method[$methodId].Runspace = Add-PodeRunspace -PassThru -Type Logs -ScriptBlock $PodeContext.Server.Logging.Method[$methodId].ScriptBlock -Parameters @{ MethodId = $methodId } -Name 'Method' | Out-Null } } } From 64ab1c9fb0fd46813cdec5e08c5d8410cf485744 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Thu, 6 Feb 2025 06:13:17 -0800 Subject: [PATCH 38/53] Update Pode.psd1 --- src/Locales/ja/Pode.psd1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Locales/ja/Pode.psd1 b/src/Locales/ja/Pode.psd1 index 535c707aa..cdf43525c 100644 --- a/src/Locales/ja/Pode.psd1 +++ b/src/Locales/ja/Pode.psd1 @@ -153,7 +153,6 @@ parameterNotSuppliedInRequestExceptionMessage = "リクエストに '{0}' という名前のパラメータが提供されていないか、データがありません。" cacheStorageNotFoundForSetExceptionMessage = "キャッシュされたアイテム '{1}' を設定しようとしたときに、名前 '{0}' のキャッシュストレージが見つかりません。" methodPathAlreadyDefinedExceptionMessage = '[{0}] {1}: 既に定義されています。' - errorLoggingAlreadyEnabledExceptionMessage = 'エラーロギングは既に有効になっています。' valueForUsingVariableNotFoundExceptionMessage = "'`$using:{0}'の値が見つかりませんでした。" rapidPdfDoesNotSupportOpenApi31ExceptionMessage = 'ドキュメントツール RapidPdf は OpenAPI 3.1 をサポートしていません' oauth2ClientSecretRequiredExceptionMessage = 'PKCEを使用しない場合、OAuth2にはクライアントシークレットが必要です。' @@ -281,10 +280,12 @@ invalidSchemeForAuthValidatorExceptionMessage = "'{1}'認証バリデーターのために提供された'{0}'スキームには有効なScriptBlockが必要です。" sseFailedToBroadcastExceptionMessage = '{0}のSSEブロードキャストレベルが定義されているため、SSEのブロードキャストに失敗しました: {1}' adModuleWindowsOnlyExceptionMessage = 'Active DirectoryモジュールはWindowsでのみ利用可能です。' - requestLoggingAlreadyEnabledExceptionMessage = 'リクエストロギングは既に有効になっています。' invalidAccessControlMaxAgeDurationExceptionMessage = '無効な Access-Control-Max-Age 期間が提供されました:{0}。0 より大きくする必要があります。' openApiDefinitionAlreadyExistsExceptionMessage = '名前が {0} の OpenAPI 定義は既に存在します。' renamePodeOADefinitionTagExceptionMessage = "Rename-PodeOADefinitionTag は Select-PodeOADefinition 'ScriptBlock' 内で使用できません。" + loggingAlreadyEnabledExceptionMessage = "ログ記録 '{0}' は既に有効になっています。" + invalidEncodingExceptionMessage = '無効なエンコーディング: {0}' + syslogProtocolExceptionMessage = 'SyslogプロトコルはRFC3164またはRFC5424のみを使用できます。' taskProcessDoesNotExistExceptionMessage = 'タスクプロセスが存在しません: {0}' scheduleProcessDoesNotExistExceptionMessage = 'スケジュールプロセスが存在しません: {0}' definitionTagChangeNotAllowedExceptionMessage = 'Routeの定義タグは変更できません。' From 99e7b43101a5756c4a07cb9371fec86d2066a289 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Thu, 6 Feb 2025 06:31:33 -0800 Subject: [PATCH 39/53] minor changes --- src/Private/Server.ps1 | 2 -- src/Public/FileWatchers.ps1 | 2 +- src/Public/Logging.ps1 | 2 ++ 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Private/Server.ps1 b/src/Private/Server.ps1 index 5d3b56c92..f54c14449 100644 --- a/src/Private/Server.ps1 +++ b/src/Private/Server.ps1 @@ -255,8 +255,6 @@ function Restart-PodeInternalServer { $PodeContext.Server.Views.Clear() $PodeContext.Timers.Items.Clear() - $PodeContext.Server.Logging.Type.Clear() - $PodeContext.Server.Logging.Method.Clear() Clear-PodeLogging # clear schedules diff --git a/src/Public/FileWatchers.ps1 b/src/Public/FileWatchers.ps1 index 67c8468d7..e1cda2023 100644 --- a/src/Public/FileWatchers.ps1 +++ b/src/Public/FileWatchers.ps1 @@ -325,4 +325,4 @@ function Use-PodeFileWatchers { ) Use-PodeFolder -Path $Path -DefaultPath 'filewatchers' -} \ No newline at end of file +} diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 8192bd263..f972b1987 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -1228,6 +1228,8 @@ function Disable-PodeLog { Clear-PodeLogging #> function Clear-PodeLogging { + $PodeContext.Server.Logging.Type.Clear() + $PodeContext.Server.Logging.Method.Clear() [pode.PodeLogger]::Clear() } From 01e5f1671ab32701aaf920b281a66956a368cc1f Mon Sep 17 00:00:00 2001 From: mdaneri Date: Fri, 7 Feb 2025 04:07:35 -0800 Subject: [PATCH 40/53] Update Server.Tests.ps1 --- tests/unit/Server.Tests.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/Server.Tests.ps1 b/tests/unit/Server.Tests.ps1 index a67c0941e..f5ceb62cc 100644 --- a/tests/unit/Server.Tests.ps1 +++ b/tests/unit/Server.Tests.ps1 @@ -119,8 +119,7 @@ Describe 'Restart-PodeInternalServer' { Mock Start-PodeInternalServer { } Mock Write-PodeErrorLog { } Mock Close-PodeDisposable { } - Mock Invoke-PodeEvent { } - Mock Clear-PodeLogging { } + Mock Invoke-PodeEvent { } } It 'Resetting the server values' { From 115515b4a8d2b2c746fcd867768dc73adc4b4174 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Fri, 7 Feb 2025 08:05:03 -0800 Subject: [PATCH 41/53] . --- src/Public/Logging.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index f972b1987..434d518c1 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -850,7 +850,6 @@ function Write-PodeErrorLog { } } - <# .SYNOPSIS Writes an object, exception, or custom message to a configured custom or built-in logging method. From 282875e3271bf0ffc50f3cd6feb425da96f51d19 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 22 Feb 2025 07:56:15 -0800 Subject: [PATCH 42/53] Update OpenApi.Tests.ps1 --- tests/integration/OpenApi.Tests.ps1 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/integration/OpenApi.Tests.ps1 b/tests/integration/OpenApi.Tests.ps1 index cc398cdd4..de7ebe124 100644 --- a/tests/integration/OpenApi.Tests.ps1 +++ b/tests/integration/OpenApi.Tests.ps1 @@ -149,7 +149,7 @@ Describe 'OpenAPI integration tests' { return $true } - Start-Sleep -Seconds 5 + Start-Sleep -Seconds 20 } AfterAll { @@ -160,8 +160,7 @@ Describe 'OpenAPI integration tests' { Describe 'OpenAPI' { it 'Open API v3.0.3' { - - Start-Sleep -Seconds 10 + $fileContent = Get-Content -Path "$PSScriptRoot/specs/OpenApi-TuttiFrutti_3.0.3.json" $webResponse = Invoke-WebRequest -Uri "http://localhost:$($PortV3)/docs/openapi/v3.0" -Method Get From a19e5d34402b1f230ea4ba53e250d8d9134c9cb9 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 22 Feb 2025 09:25:49 -0800 Subject: [PATCH 43/53] merged --- src/Listener/PodeRequest.cs | 4 ++-- src/Listener/PodeSignalRequest.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Listener/PodeRequest.cs b/src/Listener/PodeRequest.cs index 0576d3845..53c2ea84e 100644 --- a/src/Listener/PodeRequest.cs +++ b/src/Listener/PodeRequest.cs @@ -268,7 +268,7 @@ public async Task Receive(CancellationToken cancellationToken) } catch (Exception ex) when (ex is IOException || ex is ObjectDisposedException) { - PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Debug); + PodeLogger.LogException(ex, Context.Listener, PodeLoggingLevel.Debug); break; } if (read <= 0) @@ -460,7 +460,7 @@ public virtual void PartialDispose() } catch (Exception ex) { - PodeHelpers.WriteException(ex, Context.Listener, PodeLoggingLevel.Error); + PodeLogger.LogException(ex, Context.Listener, PodeLoggingLevel.Error); } } diff --git a/src/Listener/PodeSignalRequest.cs b/src/Listener/PodeSignalRequest.cs index 9b338a75a..439a871f3 100644 --- a/src/Listener/PodeSignalRequest.cs +++ b/src/Listener/PodeSignalRequest.cs @@ -138,7 +138,7 @@ protected override void Dispose(bool disposing) if (disposing) { // Send close frame - PodeLogger.WriteErrorMessage($"Closing Websocket", Context.Listener, PodeLoggingLevel.Verbose, Context); + PodeLogger.LogMessage($"Closing Websocket", Context.Listener, PodeLoggingLevel.Verbose, Context); // Wait for the close frame to be sent Context.Response.WriteFrame(string.Empty, PodeWsOpCode.Close).Wait(); From c4b8ec25ff81f82cbf15e55c90e2de79624b7be4 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 1 Mar 2025 19:06:45 -0800 Subject: [PATCH 44/53] update test --- src/Private/Console.ps1 | 1 + src/Private/Context.ps1 | 7 +- src/Public/Core.ps1 | 1 + ss.txt | 12 ++ tests/integration/OpenApi.Tests.ps1 | 138 +---------------- tests/shared/TestHelper.ps1 | 221 ++++++++++++++++++++++++++++ 6 files changed, 247 insertions(+), 133 deletions(-) create mode 100644 ss.txt diff --git a/src/Private/Console.ps1 b/src/Private/Console.ps1 index 91cb4a442..a3cb52f3c 100644 --- a/src/Private/Console.ps1 +++ b/src/Private/Console.ps1 @@ -1335,6 +1335,7 @@ function Set-PodeConsoleOverrideConfiguration { $PodeContext.Server.Console.Quiet = $true $PodeContext.Server.Console.DisableConsoleInput = $true $PodeContext.Server.Console.DisableTermination = $true + $PodeContext.Server.Console.Daemon = $true } # Apply IIS-specific overrides diff --git a/src/Private/Context.ps1 b/src/Private/Context.ps1 index 0845d4423..8a9437ece 100644 --- a/src/Private/Context.ps1 +++ b/src/Private/Context.ps1 @@ -103,7 +103,10 @@ function New-PodeContext { $IgnoreServerConfig, [string] - $ConfigFile + $ConfigFile, + + [switch] + $Daemon ) # set a random server name if one not supplied @@ -346,7 +349,7 @@ function New-PodeContext { $ctx.Server.IsHeroku = (!$isServerless -and (!(Test-PodeIsEmpty $env:PORT)) -and (!(Test-PodeIsEmpty $env:DYNO))) # Check if the current session is running in a console-like environment - if (Test-PodeHasConsole) { + if (Test-PodeHasConsole -and ! $Daemon) { try { if (! (Test-PodeIsISEHost)) { # If the session is not configured for quiet mode, modify console behavior diff --git a/src/Public/Core.ps1 b/src/Public/Core.ps1 index 5aa211be9..519cd43f1 100644 --- a/src/Public/Core.ps1 +++ b/src/Public/Core.ps1 @@ -268,6 +268,7 @@ function Start-PodeServer { EnableBreakpoints = $EnableBreakpoints IgnoreServerConfig = $IgnoreServerConfig ConfigFile = $ConfigFile + Daemon = $Daemon } diff --git a/ss.txt b/ss.txt new file mode 100644 index 000000000..e3dbd8f68 --- /dev/null +++ b/ss.txt @@ -0,0 +1,12 @@ +__pode_log_errors__ +Write-PodeLog: C:\Users\m_dan\Documents\GitHub\Pode\src\Public\Logging.ps1:850:9 +Line | + 850 | Write-PodeLog @PSBoundParameters -name $name -SuppressErrorLo … + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | Cannot index into a null array. + + +Key Value +--- ----- +ErrorRecord Exception setting "TreatControlCAsInput": "The parameter is incorrect." + diff --git a/tests/integration/OpenApi.Tests.ps1 b/tests/integration/OpenApi.Tests.ps1 index de7ebe124..449390156 100644 --- a/tests/integration/OpenApi.Tests.ps1 +++ b/tests/integration/OpenApi.Tests.ps1 @@ -5,6 +5,10 @@ param() Describe 'OpenAPI integration tests' { BeforeAll { + + $helperPath = (Split-Path -Parent -Path $PSCommandPath) -ireplace 'integration', 'shared' + . "$helperPath/TestHelper.ps1" + $mindyCommonHeaders = @{ 'accept' = 'application/json' 'X-API-KEY' = 'test2-api-key' @@ -19,137 +23,9 @@ Describe 'OpenAPI integration tests' { $PortV3 = 8080 $PortV3_1 = 8081 $scriptPath = "$($PSScriptRoot)\..\..\examples\OpenApi-TuttiFrutti.ps1" - Start-Process (Get-Process -Id $PID).Path -ArgumentList "-NoProfile -File `"$scriptPath`" -PortV3 $PortV3 -PortV3_1 $PortV3_1 -Daemon -IgnoreServerConfig" -NoNewWindow - - function Compare-StringRnLn { - param ( - [string]$InputString1, - [string]$InputString2 - ) - return ($InputString1.Trim() -replace "`r`n|`n|`r", "`n") -eq ($InputString2.Trim() -replace "`r`n|`n|`r", "`n") - } - - function Convert-PsCustomObjectToOrderedHashtable { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [PSCustomObject]$InputObject - ) - begin { - # Define a recursive function within the process block - function Convert-ObjectRecursively { - param ( - [Parameter(Mandatory = $true)] - [System.Object] - $InputObject - ) - - # Initialize an ordered dictionary - $orderedHashtable = [ordered]@{} - - # Loop through each property of the PSCustomObject - foreach ($property in $InputObject.PSObject.Properties) { - # Check if the property value is a PSCustomObject - if ($property.Value -is [PSCustomObject]) { - # Recursively convert the nested PSCustomObject - $orderedHashtable[$property.Name] = Convert-ObjectRecursively -InputObject $property.Value - } - elseif ($property.Value -is [System.Collections.IEnumerable] -and -not ($property.Value -is [string])) { - # If the value is a collection, check each element - $convertedCollection = @() - foreach ($item in $property.Value) { - if ($item -is [PSCustomObject]) { - $convertedCollection += Convert-ObjectRecursively -InputObject $item - } - else { - $convertedCollection += $item - } - } - $orderedHashtable[$property.Name] = $convertedCollection - } - else { - # Add the property name and value to the ordered hashtable - $orderedHashtable[$property.Name] = $property.Value - } - } - - # Return the resulting ordered hashtable - return $orderedHashtable - } - } - process { - # Call the recursive helper function for each input object - Convert-ObjectRecursively -InputObject $InputObject - } - } - - function Compare-Hashtable { - param ( - [object]$Hashtable1, - [object]$Hashtable2 - ) - - # Function to compare two hashtable values - function Compare-Value($value1, $value2) { - # Check if both values are hashtables - if ((($value1 -is [hashtable] -or $value1 -is [System.Collections.Specialized.OrderedDictionary]) -and - ($value2 -is [hashtable] -or $value2 -is [System.Collections.Specialized.OrderedDictionary]))) { - return Compare-Hashtable -Hashtable1 $value1 -Hashtable2 $value2 - } - # Check if both values are arrays - elseif (($value1 -is [Object[]]) -and ($value2 -is [Object[]])) { - if ($value1.Count -ne $value2.Count) { - return $false - } - for ($i = 0; $i -lt $value1.Count; $i++) { - $found = $false - for ($j = 0; $j -lt $value2.Count; $j++) { - if ( Compare-Value $value1[$i] $value2[$j]) { - $found = $true - } - } - if ($found -eq $false) { - return $false - } - } - return $true - } - else { - if ($value1 -is [string] -and $value2 -is [string]) { - return Compare-StringRnLn $value1 $value2 - } - # Check if the values are equal - return $value1 -eq $value2 - } - } + Start-Process (Get-Process -Id $PID).Path -ArgumentList "-NoProfile -File `"$scriptPath`" -PortV3 $PortV3 -PortV3_1 $PortV3_1 -Daemon -IgnoreServerConfig" -NoNewWindow - $keys1 = $Hashtable1.Keys - $keys2 = $Hashtable2.Keys - - # Check if both hashtables have the same keys - if ($keys1.Count -ne $keys2.Count) { - return $false - } - - foreach ($key in $keys1) { - if (! ($Hashtable2.Keys -contains $key)) { - return $false - } - - if ($Hashtable2[$key] -is [hashtable] -or $Hashtable2[$key] -is [System.Collections.Specialized.OrderedDictionary]) { - if (! (Compare-Hashtable -Hashtable1 $Hashtable1[$key] -Hashtable2 $Hashtable2[$key])) { - return $false - } - } - elseif (!(Compare-Value $Hashtable1[$key] $Hashtable2[$key])) { - return $false - } - } - - return $true - } - - Start-Sleep -Seconds 20 + Wait-ForWebServer -Port $PortV3 } AfterAll { @@ -160,7 +36,7 @@ Describe 'OpenAPI integration tests' { Describe 'OpenAPI' { it 'Open API v3.0.3' { - + $fileContent = Get-Content -Path "$PSScriptRoot/specs/OpenApi-TuttiFrutti_3.0.3.json" $webResponse = Invoke-WebRequest -Uri "http://localhost:$($PortV3)/docs/openapi/v3.0" -Method Get diff --git a/tests/shared/TestHelper.ps1 b/tests/shared/TestHelper.ps1 index 38ccacbe2..506acf841 100644 --- a/tests/shared/TestHelper.ps1 +++ b/tests/shared/TestHelper.ps1 @@ -47,3 +47,224 @@ function Import-PodeAssembly { Add-Type -LiteralPath (Join-Path -Path $netFolder -ChildPath 'Pode.dll') -ErrorAction Stop } } + + + +function Compare-StringRnLn { + param ( + [string]$InputString1, + [string]$InputString2 + ) + return ($InputString1.Trim() -replace "`r`n|`n|`r", "`n") -eq ($InputString2.Trim() -replace "`r`n|`n|`r", "`n") +} + + +function Convert-PsCustomObjectToOrderedHashtable { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSCustomObject]$InputObject + ) + begin { + # Define a recursive function within the process block + function Convert-ObjectRecursively { + param ( + [Parameter(Mandatory = $true)] + [System.Object] + $InputObject + ) + + # Initialize an ordered dictionary + $orderedHashtable = [ordered]@{} + + # Loop through each property of the PSCustomObject + foreach ($property in $InputObject.PSObject.Properties) { + # Check if the property value is a PSCustomObject + if ($property.Value -is [PSCustomObject]) { + # Recursively convert the nested PSCustomObject + $orderedHashtable[$property.Name] = Convert-ObjectRecursively -InputObject $property.Value + } + elseif ($property.Value -is [System.Collections.IEnumerable] -and -not ($property.Value -is [string])) { + # If the value is a collection, check each element + $convertedCollection = @() + foreach ($item in $property.Value) { + if ($item -is [PSCustomObject]) { + $convertedCollection += Convert-ObjectRecursively -InputObject $item + } + else { + $convertedCollection += $item + } + } + $orderedHashtable[$property.Name] = $convertedCollection + } + else { + # Add the property name and value to the ordered hashtable + $orderedHashtable[$property.Name] = $property.Value + } + } + + # Return the resulting ordered hashtable + return $orderedHashtable + } + } + process { + # Call the recursive helper function for each input object + Convert-ObjectRecursively -InputObject $InputObject + } +} + +function Compare-Hashtable { + param ( + [object]$Hashtable1, + [object]$Hashtable2 + ) + + # Function to compare two hashtable values + function Compare-Value($value1, $value2) { + # Check if both values are hashtables + if ((($value1 -is [hashtable] -or $value1 -is [System.Collections.Specialized.OrderedDictionary]) -and + ($value2 -is [hashtable] -or $value2 -is [System.Collections.Specialized.OrderedDictionary]))) { + return Compare-Hashtable -Hashtable1 $value1 -Hashtable2 $value2 + } + # Check if both values are arrays + elseif (($value1 -is [Object[]]) -and ($value2 -is [Object[]])) { + if ($value1.Count -ne $value2.Count) { + return $false + } + for ($i = 0; $i -lt $value1.Count; $i++) { + $found = $false + for ($j = 0; $j -lt $value2.Count; $j++) { + if ( Compare-Value $value1[$i] $value2[$j]) { + $found = $true + } + } + if ($found -eq $false) { + return $false + } + } + return $true + } + else { + if ($value1 -is [string] -and $value2 -is [string]) { + return Compare-StringRnLn $value1 $value2 + } + # Check if the values are equal + return $value1 -eq $value2 + } + } + + $keys1 = $Hashtable1.Keys + $keys2 = $Hashtable2.Keys + + # Check if both hashtables have the same keys + if ($keys1.Count -ne $keys2.Count) { + return $false + } + + foreach ($key in $keys1) { + if (! ($Hashtable2.Keys -contains $key)) { + return $false + } + + if ($Hashtable2[$key] -is [hashtable] -or $Hashtable2[$key] -is [System.Collections.Specialized.OrderedDictionary]) { + if (! (Compare-Hashtable -Hashtable1 $Hashtable1[$key] -Hashtable2 $Hashtable2[$key])) { + return $false + } + } + elseif (!(Compare-Value $Hashtable1[$key] $Hashtable2[$key])) { + return $false + } + } + + return $true +} + + +<# +.SYNOPSIS + Waits for a web server to become available at a specified URI or port. + +.DESCRIPTION + This function continuously checks if a web server is online by sending an HTTP request. + It retries until the server responds with a 200 status code or a timeout is reached. + +.PARAMETER Uri + The full URI to check (e.g., "http://127.0.0.1:5000"). If not provided, defaults to "http://localhost:$Port". + +.PARAMETER Port + The port on which the web server is expected to be available. If no URI is provided, the function constructs a default URI using "http://localhost:$Port". + +.PARAMETER Timeout + The maximum number of seconds to wait before timing out. Default is 60 seconds. + +.PARAMETER Interval + The number of seconds to wait between retries. Default is 2 seconds. + +.OUTPUTS + Boolean - Returns $true if the server is online, otherwise $false. + +.EXAMPLE + Wait-ForWebServer -Port 8080 -Timeout 30 -Interval 2 + + Waits up to 30 seconds for the web server on port 8080 to come online. + +.EXAMPLE + Wait-ForWebServer -Uri "http://127.0.0.1:5000" -Timeout 45 + + Waits up to 45 seconds for the web server at "http://127.0.0.1:5000" to respond. + +.NOTES + Author: ChatGPT + This function ensures that the web server is fully responding, not just that the port is open. +#> +function Wait-ForWebServer { + [CmdletBinding()] + [OutputType([bool])] + param ( + [Parameter(Position = 0)] + [string]$Uri, + + [Parameter(Position = 1)] + [int]$Port, + + [Parameter()] + [int]$Timeout = 60, + + [Parameter()] + [int]$Interval = 2 + ) + + # Determine the final URI: If no URI is provided, use "http://localhost:$Port" + if (-not $Uri) { + if ($Port -gt 0) { + $Uri = "http://localhost:$Port" + } + else { + return $false + } + } + + $MaxRetries = [math]::Ceiling($Timeout / $Interval) + $RetryCount = 0 + + while ($RetryCount -lt $MaxRetries) { + try { + # Send a request but ignore status codes (any response means the server is online) + $null = Invoke-WebRequest -Uri $Uri -UseBasicParsing -TimeoutSec 3 + return $true + } + catch { + if ($_.Exception.Response -and $_.Exception.Response.StatusCode -eq 404) { + return $true + } + else { + Write-Debug "Waiting for webserver to come online at $Uri... (Attempt $($RetryCount+1)/$MaxRetries)" + } + } + + Start-Sleep -Seconds $Interval + $RetryCount++ + } + + return $false +} From 79461e8f525cd724fbab08782559a236c81a3f95 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sun, 2 Mar 2025 07:22:24 -0800 Subject: [PATCH 45/53] remove a $_ | Write-PodeErrorLog when log doesn't exist --- src/Locales/ar/Pode.psd1 | 1 + src/Locales/de/Pode.psd1 | 1 + src/Locales/en-us/Pode.psd1 | 1 + src/Locales/en/Pode.psd1 | 1 + src/Locales/es/Pode.psd1 | 1 + src/Locales/fr/Pode.psd1 | 1 + src/Locales/it/Pode.psd1 | 1 + src/Locales/ja/Pode.psd1 | 1 + src/Locales/ko/Pode.psd1 | 1 + src/Locales/nl/Pode.psd1 | 1 + src/Locales/pl/Pode.psd1 | 1 + src/Locales/pt/Pode.psd1 | 1 + src/Locales/zh/Pode.psd1 | 1 + src/Private/Context.ps1 | 4 +++- src/Private/Logging.ps1 | 8 ++++++-- src/Public/Logging.ps1 | 11 +++++++++++ ss.txt | 12 ------------ tests/unit/Logging.Tests.ps1 | 7 +++++-- 18 files changed, 38 insertions(+), 17 deletions(-) delete mode 100644 ss.txt diff --git a/src/Locales/ar/Pode.psd1 b/src/Locales/ar/Pode.psd1 index 72e2f6829..2810e39d3 100644 --- a/src/Locales/ar/Pode.psd1 +++ b/src/Locales/ar/Pode.psd1 @@ -328,4 +328,5 @@ rateLimitRuleDoesNotExistExceptionMessage = 'قاعدة الحد الأقصى للمعدل غير موجودة: {0}' accessLimitRuleAlreadyExistsExceptionMessage = 'تم تعريف قاعدة الحد الأقصى للوصول بالفعل: {0}' accessLimitRuleDoesNotExistExceptionMessage = 'قاعدة الحد الأقصى للوصول غير موجودة: {0}' + loggerDoesNotExistExceptionMessage = "المسجل '{0}' غير موجود." } diff --git a/src/Locales/de/Pode.psd1 b/src/Locales/de/Pode.psd1 index 05fb31552..7db360d19 100644 --- a/src/Locales/de/Pode.psd1 +++ b/src/Locales/de/Pode.psd1 @@ -328,4 +328,5 @@ rateLimitRuleDoesNotExistExceptionMessage = "Die Rate-Limit-Regel mit dem Namen '{0}' existiert nicht." accessLimitRuleAlreadyExistsExceptionMessage = "Die Zugriffsbeschränkungsregel mit dem Namen '{0}' existiert bereits." accessLimitRuleDoesNotExistExceptionMessage = "Die Zugriffsbeschränkungsregel mit dem Namen '{0}' existiert nicht." + loggerDoesNotExistExceptionMessage = "Logger '{0}' existiert nicht." } \ No newline at end of file diff --git a/src/Locales/en-us/Pode.psd1 b/src/Locales/en-us/Pode.psd1 index e74d35f2c..d45f4a654 100644 --- a/src/Locales/en-us/Pode.psd1 +++ b/src/Locales/en-us/Pode.psd1 @@ -328,4 +328,5 @@ accessLimitRuleAlreadyExistsExceptionMessage = "An access limit rule with the name '{0}' already exists." accessLimitRuleDoesNotExistExceptionMessage = "An access limit rule with the name '{0}' does not exist." deprecatedFunctionWarningMessage = "WARNING: The function '{0}' is deprecated and will be removed in future releases. Please use the '{1}' function instead." + loggerDoesNotExistExceptionMessage = "Logger '{0}' does not exist." } \ No newline at end of file diff --git a/src/Locales/en/Pode.psd1 b/src/Locales/en/Pode.psd1 index b9eeaf78a..eb2e503d5 100644 --- a/src/Locales/en/Pode.psd1 +++ b/src/Locales/en/Pode.psd1 @@ -328,4 +328,5 @@ rateLimitRuleDoesNotExistExceptionMessage = "A Rate Limit Rule with the name '{0}' does not exist." accessLimitRuleAlreadyExistsExceptionMessage = "An Access Limit Rule with the name '{0}' already exists." accessLimitRuleDoesNotExistExceptionMessage = "An Access Limit Rule with the name '{0}' does not exist." + loggerDoesNotExistExceptionMessage = "Logger '{0}' does not exist." } \ No newline at end of file diff --git a/src/Locales/es/Pode.psd1 b/src/Locales/es/Pode.psd1 index 7b9c1d51c..f86926a2f 100644 --- a/src/Locales/es/Pode.psd1 +++ b/src/Locales/es/Pode.psd1 @@ -328,4 +328,5 @@ rateLimitRuleDoesNotExistExceptionMessage = "La regla de límite de velocidad con el nombre '{0}' no existe." accessLimitRuleAlreadyExistsExceptionMessage = "La regla de límite de acceso con el nombre '{0}' ya existe." accessLimitRuleDoesNotExistExceptionMessage = "La regla de límite de acceso con el nombre '{0}' no existe." + loggerDoesNotExistExceptionMessage = "El registrador '{0}' no existe." } \ No newline at end of file diff --git a/src/Locales/fr/Pode.psd1 b/src/Locales/fr/Pode.psd1 index 6219ffeb2..313ee151c 100644 --- a/src/Locales/fr/Pode.psd1 +++ b/src/Locales/fr/Pode.psd1 @@ -328,4 +328,5 @@ rateLimitRuleDoesNotExistExceptionMessage = "La règle de limite de taux '{0}' n'existe pas." accessLimitRuleAlreadyExistsExceptionMessage = "Une règle de limite d'accès nommée '{0}' existe déjà." accessLimitRuleDoesNotExistExceptionMessage = "La règle de limite d'accès '{0}' n'existe pas." + loggerDoesNotExistExceptionMessage = "Le journaliseur '{0}' n'existe pas." } \ No newline at end of file diff --git a/src/Locales/it/Pode.psd1 b/src/Locales/it/Pode.psd1 index e799c5f6c..30b7ce678 100644 --- a/src/Locales/it/Pode.psd1 +++ b/src/Locales/it/Pode.psd1 @@ -328,4 +328,5 @@ rateLimitRuleDoesNotExistExceptionMessage = "La regola di limitazione del tasso con il nome '{0}' non esiste." accessLimitRuleAlreadyExistsExceptionMessage = "Una regola di limitazione dell'accesso con il nome '{0}' esiste già." accessLimitRuleDoesNotExistExceptionMessage = "La regola di limitazione dell'accesso con il nome '{0}' non esiste." + loggerDoesNotExistExceptionMessage = "Il logger '{0}' non esiste." } \ No newline at end of file diff --git a/src/Locales/ja/Pode.psd1 b/src/Locales/ja/Pode.psd1 index 2d14d8a2e..becba3aad 100644 --- a/src/Locales/ja/Pode.psd1 +++ b/src/Locales/ja/Pode.psd1 @@ -328,4 +328,5 @@ rateLimitRuleDoesNotExistExceptionMessage = "名前が '{0}' のレート制限ルールは存在しません。" accessLimitRuleAlreadyExistsExceptionMessage = "名前が '{0}' のアクセス制限ルールは既に存在します。" accessLimitRuleDoesNotExistExceptionMessage = "名前が '{0}' のアクセス制限ルールは存在しません。" + loggerDoesNotExistExceptionMessage = "ロガー '{0}' は存在しません。" } \ No newline at end of file diff --git a/src/Locales/ko/Pode.psd1 b/src/Locales/ko/Pode.psd1 index 3c4f8f906..29c82a1b1 100644 --- a/src/Locales/ko/Pode.psd1 +++ b/src/Locales/ko/Pode.psd1 @@ -328,4 +328,5 @@ rateLimitRuleDoesNotExistExceptionMessage = "이름이 '{0}'인 비율 제한 규칙이 존재하지 않습니다." accessLimitRuleAlreadyExistsExceptionMessage = "이름이 '{0}'인 액세스 제한 규칙이 이미 존재합니다." accessLimitRuleDoesNotExistExceptionMessage = "이름이 '{0}'인 액세스 제한 규칙이 존재하지 않습니다." + loggerDoesNotExistExceptionMessage = "로거 '{0}'가 존재하지 않습니다." } \ No newline at end of file diff --git a/src/Locales/nl/Pode.psd1 b/src/Locales/nl/Pode.psd1 index 3190e1bb9..487f391dc 100644 --- a/src/Locales/nl/Pode.psd1 +++ b/src/Locales/nl/Pode.psd1 @@ -328,4 +328,5 @@ rateLimitRuleDoesNotExistExceptionMessage = "Rate Limit-regel met de naam '{0}' bestaat niet." accessLimitRuleAlreadyExistsExceptionMessage = "Toegangslimietregel met de naam '{0}' bestaat al." accessLimitRuleDoesNotExistExceptionMessage = "Toegangslimietregel met de naam '{0}' bestaat niet." + loggerDoesNotExistExceptionMessage = "Logger '{0}' bestaat niet." } \ No newline at end of file diff --git a/src/Locales/pl/Pode.psd1 b/src/Locales/pl/Pode.psd1 index caabaef6f..16bf4f072 100644 --- a/src/Locales/pl/Pode.psd1 +++ b/src/Locales/pl/Pode.psd1 @@ -328,4 +328,5 @@ rateLimitRuleDoesNotExistExceptionMessage = "Reguła limitu szybkości o nazwie '{0}' nie istnieje." accessLimitRuleAlreadyExistsExceptionMessage = "Reguła limitu dostępu o nazwie '{0}' już istnieje." accessLimitRuleDoesNotExistExceptionMessage = "Reguła limitu dostępu o nazwie '{0}' nie istnieje." + loggerDoesNotExistExceptionMessage = "Logger '{0}' nie istnieje." } \ No newline at end of file diff --git a/src/Locales/pt/Pode.psd1 b/src/Locales/pt/Pode.psd1 index 39f270cb9..ae88065d4 100644 --- a/src/Locales/pt/Pode.psd1 +++ b/src/Locales/pt/Pode.psd1 @@ -328,4 +328,5 @@ rateLimitRuleDoesNotExistExceptionMessage = "A regra de limite de taxa com o nome '{0}' não existe." accessLimitRuleAlreadyExistsExceptionMessage = "A regra de limite de acesso com o nome '{0}' já existe." accessLimitRuleDoesNotExistExceptionMessage = "A regra de limite de acesso com o nome '{0}' não existe." + loggerDoesNotExistExceptionMessage = "O logger '{0}' não existe." } \ No newline at end of file diff --git a/src/Locales/zh/Pode.psd1 b/src/Locales/zh/Pode.psd1 index aae195ae4..246d260f9 100644 --- a/src/Locales/zh/Pode.psd1 +++ b/src/Locales/zh/Pode.psd1 @@ -328,4 +328,5 @@ rateLimitRuleDoesNotExistExceptionMessage = '速率限制规则不存在: {0}' accessLimitRuleAlreadyExistsExceptionMessage = '访问限制规则已存在: {0}' accessLimitRuleDoesNotExistExceptionMessage = '访问限制规则不存在: {0}' + loggerDoesNotExistExceptionMessage = "日志记录器 '{0}' 不存在。" } \ No newline at end of file diff --git a/src/Private/Context.ps1 b/src/Private/Context.ps1 index 8a9437ece..9b0e76ea4 100644 --- a/src/Private/Context.ps1 +++ b/src/Private/Context.ps1 @@ -47,6 +47,9 @@ using namespace Pode .PARAMETER EnableBreakpoints A switch to enable debugging breakpoints. +.PARAMETER Daemon + Configures the server to run as a daemon with minimal console interaction and output. + .EXAMPLE $context = New-PodeContext -ScriptBlock $script -FilePath 'path/to/file' -Threads 4 -ServerRoot 'path/to/root' #> @@ -369,7 +372,6 @@ function New-PodeContext { } } catch { - $_ | Write-PodeErrorLog # Console support is partial , configure the context for non-console behavior $ctx.Server.Console.DisableTermination = $true # Prevent termination $ctx.Server.Console.DisableConsoleInput = $true # Disable console input diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index 1936afcb1..cbb73ec08 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -475,7 +475,9 @@ function Get-PodeLogger { [string] $Name ) - + if (!$PodeContext.Server.Logging.Type.ContainsKey($Name)) { + throw $PodeLocale.loggerDoesNotExistExceptionMessage + } return $PodeContext.Server.Logging.Type[$Name] } @@ -503,7 +505,9 @@ function Test-PodeStandardLogger { [string] $Name ) - + if (!$PodeContext.Server.Logging.Type.ContainsKey($Name)) { + throw $PodeLocale.loggerDoesNotExistExceptionMessage + } # Check if the specified logger is a standard logger return $PodeContext.Server.Logging.Type[$Name].Standard } diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 434d518c1..bb07b32bb 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -746,6 +746,8 @@ function Remove-PodeLogger { # Finally, remove the logging type from the Types collection $null = $PodeContext.Server.Logging.Type.Remove($Name) + }else{ + throw $PodeLocale.loggerDoesNotExistExceptionMessage } } } @@ -960,12 +962,21 @@ function Write-PodeLog { ) begin { + if ($null -eq $PodeContext.Server.Logging) { + Write-Debug 'Pode not yet initialised' + return + } + if (!$Name) { $Name = [Pode.PodeLogger]::DefaultLogName } + if (!$PodeContext.Server.Logging.Type.ContainsKey($Name)) { + throw $PodeLocale.loggerDoesNotExistExceptionMessage + } # Get the configured log method. $log = $PodeContext.Server.Logging.Type[$Name] + } Process { # Define the log item based on the selected parameter set. diff --git a/ss.txt b/ss.txt deleted file mode 100644 index e3dbd8f68..000000000 --- a/ss.txt +++ /dev/null @@ -1,12 +0,0 @@ -__pode_log_errors__ -Write-PodeLog: C:\Users\m_dan\Documents\GitHub\Pode\src\Public\Logging.ps1:850:9 -Line | - 850 | Write-PodeLog @PSBoundParameters -name $name -SuppressErrorLo … - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - | Cannot index into a null array. - - -Key Value ---- ----- -ErrorRecord Exception setting "TreatControlCAsInput": "The parameter is incorrect." - diff --git a/tests/unit/Logging.Tests.ps1 b/tests/unit/Logging.Tests.ps1 index e31112462..4e6e62302 100644 --- a/tests/unit/Logging.Tests.ps1 +++ b/tests/unit/Logging.Tests.ps1 @@ -33,7 +33,7 @@ BeforeAll { Describe 'Get-PodeLogger' { It 'Returns null as the logger does not exist' { $PodeContext = @{ 'Server' = @{ 'Logging' = @{ 'Type' = @{}; } }; } - Get-PodeLogger -Name 'test' | Should -Be $null + { Get-PodeLogger -Name 'test' } | Should -Throw $PodeLocale.loggerDoesNotExistExceptionMessage } It 'Returns terminal logger for name' { @@ -93,7 +93,10 @@ Describe 'Write-PodeErrorLog' { Server = @{ Logging = @{ Type = @{ - test = @{ + ([Pode.PodeLogger]::ErrorLogName) = @{ + Standard = $false + } + test = @{ Standard = $false } } From a2e6474e660e8bbb0174da330ee615ca0beb2c4c Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sun, 2 Mar 2025 08:00:28 -0800 Subject: [PATCH 46/53] Ensure ErrorLoggingLevels is only set when ErrorLoggingEnabled is true in Pode listener configuration. Include #1507 fixes --- examples/sessiondata.ps1 | 43 +++++++++++++ src/Private/FileWatchers.ps1 | 2 +- src/Private/Logging.ps1 | 4 +- src/Private/PodeServer.ps1 | 2 +- src/Private/SmtpServer.ps1 | 2 +- src/Private/TcpServer.ps1 | 2 +- src/Private/WebSockets.ps1 | 2 +- src/Public/Logging.ps1 | 11 ++-- tests/integration/Sessions.Tests.ps1 | 7 ++- tests/shared/TestHelper.ps1 | 91 +++++++++++++++++++++++++++- 10 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 examples/sessiondata.ps1 diff --git a/examples/sessiondata.ps1 b/examples/sessiondata.ps1 new file mode 100644 index 000000000..323c5cd33 --- /dev/null +++ b/examples/sessiondata.ps1 @@ -0,0 +1,43 @@ +try { + # Determine the script path and Pode module path + $ScriptPath = (Split-Path -Parent -Path $MyInvocation.MyCommand.Path) + $podePath = Split-Path -Parent -Path $ScriptPath + + # Import the Pode module from the source path if it exists, otherwise from installed modules + if (Test-Path -Path "$($podePath)/src/Pode.psm1" -PathType Leaf) { + Import-Module "$($podePath)/src/Pode.psm1" -Force -ErrorAction Stop + } + else { + Import-Module -Name 'Pode' -MaximumVersion 2.99 -ErrorAction Stop + } +} +catch { throw } + +Start-PodeServer -ScriptBlock { + Add-PodeEndpoint -Address localhost -Port $Port -Protocol Http + Add-PodeRoute -Method Get -Path '/close' -ScriptBlock { + Close-PodeServer + } + + Enable-PodeSessionMiddleware -Secret 'schwifty' -Duration 5 -Extend -UseHeaders + + New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'Auth' -ScriptBlock { + param($username, $password) + + if (($username -eq 'morty') -and ($password -eq 'pickle')) { + return @{ User = @{ ID = 'M0R7Y302' } } + } + + return @{ Message = 'Invalid details supplied' } + } + + Add-PodeRoute -Method Post -Path '/auth/basic' -Authentication Auth -ScriptBlock { + $WebEvent.Session.Data.Views++ + + Write-PodeJsonResponse -Value @{ + Result = 'OK' + Username = $WebEvent.Auth.User.ID + Views = $WebEvent.Session.Data.Views + } + } +} \ No newline at end of file diff --git a/src/Private/FileWatchers.ps1 b/src/Private/FileWatchers.ps1 index e17bc9ed7..480a19dbf 100644 --- a/src/Private/FileWatchers.ps1 +++ b/src/Private/FileWatchers.ps1 @@ -13,7 +13,7 @@ function New-PodeFileWatcher { param() $watcher = [PodeWatcher]::new($PodeContext.Tokens.Cancellation.Token) $watcher.ErrorLoggingEnabled = (Test-PodeErrorLoggingEnabled) - $watcher.ErrorLoggingLevels = @(Get-PodeErrorLoggingLevel) + $watcher.ErrorLoggingLevels = If ( $listener.ErrorLoggingEnabled) { @(Get-PodeErrorLoggingLevel) } else { @() } return $watcher } diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index cbb73ec08..482c9f5a2 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -137,10 +137,10 @@ function Get-PodeLoggingFileMethod { $outString | Out-File -FilePath $path -Encoding $Options.Encoding -Append -Force # Remove log files beyond the MaxDays retention period, ensuring this runs once a day - if (($Options.MaxDays -gt 0) -and ($Options.NextClearDown -lt [DateTime]::Now.Date)) { + if (($Options.MaxDays -gt 0) -and ($Options.NextClearDown -le [DateTime]::Now.Date)) { $date = [DateTime]::Now.Date.AddDays(-$Options.MaxDays) - $null = Get-ChildItem -Path $options.Path -Filter '*.log' -Force | + $null = Get-ChildItem -Path $options.Path -Filter "$($options.Name)_*.log" -Force | Where-Object { $_.CreationTime -lt $date } | Remove-Item -Force diff --git a/src/Private/PodeServer.ps1 b/src/Private/PodeServer.ps1 index bb3356d8a..8bff9c498 100644 --- a/src/Private/PodeServer.ps1 +++ b/src/Private/PodeServer.ps1 @@ -88,7 +88,7 @@ function Start-PodeWebServer { # Create the listener $listener = & $("New-Pode$($PodeContext.Server.ListenerType)Listener") -CancellationToken $PodeContext.Tokens.Cancellation.Token $listener.ErrorLoggingEnabled = (Test-PodeErrorLoggingEnabled) - $listener.ErrorLoggingLevels = @(Get-PodeErrorLoggingLevel) + $listener.ErrorLoggingLevels = If ( $listener.ErrorLoggingEnabled) { @(Get-PodeErrorLoggingLevel) } else { @() } $listener.RequestTimeout = $PodeContext.Server.Request.Timeout $listener.RequestBodySize = $PodeContext.Server.Request.BodySize $listener.ShowServerDetails = [bool]$PodeContext.Server.Security.ServerDetails diff --git a/src/Private/SmtpServer.ps1 b/src/Private/SmtpServer.ps1 index e6ce3a762..a8becafab 100644 --- a/src/Private/SmtpServer.ps1 +++ b/src/Private/SmtpServer.ps1 @@ -67,7 +67,7 @@ function Start-PodeSmtpServer { # create the listener $listener = [PodeListener]::new($PodeContext.Tokens.Cancellation.Token) $listener.ErrorLoggingEnabled = (Test-PodeErrorLoggingEnabled) - $listener.ErrorLoggingLevels = @(Get-PodeErrorLoggingLevel) + $listener.ErrorLoggingLevels = If ( $listener.ErrorLoggingEnabled) { @(Get-PodeErrorLoggingLevel) } else { @() } $listener.RequestTimeout = $PodeContext.Server.Request.Timeout $listener.RequestBodySize = $PodeContext.Server.Request.BodySize diff --git a/src/Private/TcpServer.ps1 b/src/Private/TcpServer.ps1 index 12b829d1b..a5c63dd8c 100644 --- a/src/Private/TcpServer.ps1 +++ b/src/Private/TcpServer.ps1 @@ -61,7 +61,7 @@ function Start-PodeTcpServer { # create the listener $listener = [PodeListener]::new($PodeContext.Tokens.Cancellation.Token) $listener.ErrorLoggingEnabled = (Test-PodeErrorLoggingEnabled) - $listener.ErrorLoggingLevels = @(Get-PodeErrorLoggingLevel) + $listener.ErrorLoggingLevels = If ( $listener.ErrorLoggingEnabled) { @(Get-PodeErrorLoggingLevel) } else { @() } $listener.RequestTimeout = $PodeContext.Server.Request.Timeout $listener.RequestBodySize = $PodeContext.Server.Request.BodySize diff --git a/src/Private/WebSockets.ps1 b/src/Private/WebSockets.ps1 index cb7c92928..fb1377ce6 100644 --- a/src/Private/WebSockets.ps1 +++ b/src/Private/WebSockets.ps1 @@ -22,7 +22,7 @@ function New-PodeWebSocketReceiver { try { $receiver = [PodeReceiver]::new($PodeContext.Tokens.Cancellation.Token) $receiver.ErrorLoggingEnabled = (Test-PodeErrorLoggingEnabled) - $receiver.ErrorLoggingLevels = @(Get-PodeErrorLoggingLevel) + $receiver.ErrorLoggingLevels = If ( $listener.ErrorLoggingEnabled) { @(Get-PodeErrorLoggingLevel) } else { @() } $PodeContext.Server.WebSockets.Receiver = $receiver $PodeContext.Receivers += $receiver } diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index bb07b32bb..98741d1c0 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -746,7 +746,8 @@ function Remove-PodeLogger { # Finally, remove the logging type from the Types collection $null = $PodeContext.Server.Logging.Type.Remove($Name) - }else{ + } + else { throw $PodeLocale.loggerDoesNotExistExceptionMessage } } @@ -845,9 +846,11 @@ function Write-PodeErrorLog { Process { $name = [Pode.PodeLogger]::ErrorLogName - Write-PodeLog @PSBoundParameters -name $name -SuppressErrorLog - if ($PodeContext.Server.Logging.Type[$name].DuplicateToDefaultLog -and ! $SuppressDefaultLog) { - Write-PodeLog @PSBoundParameters -name ([Pode.PodeLogger]::DefaultLogName) -SuppressErrorLog + if (Test-PodeLoggerEnabled -Name $name) { + Write-PodeLog @PSBoundParameters -name $name -SuppressErrorLog + if ($PodeContext.Server.Logging.Type[$name].DuplicateToDefaultLog -and ! $SuppressDefaultLog) { + Write-PodeLog @PSBoundParameters -name ([Pode.PodeLogger]::DefaultLogName) -SuppressErrorLog + } } } } diff --git a/tests/integration/Sessions.Tests.ps1 b/tests/integration/Sessions.Tests.ps1 index b4d42f191..2e5aa69b9 100644 --- a/tests/integration/Sessions.Tests.ps1 +++ b/tests/integration/Sessions.Tests.ps1 @@ -5,13 +5,16 @@ param() Describe 'Session Requests' { BeforeAll { + $helperPath = (Split-Path -Parent -Path $PSCommandPath) -ireplace 'integration', 'shared' + . "$helperPath/TestHelper.ps1" + $Port = 8080 $Endpoint = "http://127.0.0.1:$($Port)" Start-Job -Name 'Pode' -ErrorAction Stop -ScriptBlock { Import-Module -Name "$($using:PSScriptRoot)\..\..\src\Pode.psm1" - Start-PodeServer -Quiet -ScriptBlock { + Start-PodeServer -Daemon -ScriptBlock { Add-PodeEndpoint -Address localhost -Port $using:Port -Protocol Http Add-PodeRoute -Method Get -Path '/close' -ScriptBlock { Close-PodeServer @@ -41,7 +44,7 @@ Describe 'Session Requests' { } } - Start-Sleep -Seconds 10 + Wait-ForWebServer -Port $Port } AfterAll { diff --git a/tests/shared/TestHelper.ps1 b/tests/shared/TestHelper.ps1 index 506acf841..f8168b34d 100644 --- a/tests/shared/TestHelper.ps1 +++ b/tests/shared/TestHelper.ps1 @@ -1,3 +1,5 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '')] +param() <# .SYNOPSIS Ensures the Pode assembly is loaded into the current session. @@ -49,7 +51,35 @@ function Import-PodeAssembly { } +<# +.SYNOPSIS + Compares two strings while normalizing line endings. + +.DESCRIPTION + This function trims both input strings and replaces all variations of line endings (`CRLF`, `LF`, `CR`) with a normalized `LF` (`\n`). + It then compares the normalized strings for equality. + +.PARAMETER InputString1 + The first string to compare. + +.PARAMETER InputString2 + The second string to compare. +.OUTPUTS + [bool] + Returns `$true` if both strings are equal after normalization; otherwise, returns `$false`. + +.EXAMPLE + Compare-StringRnLn -InputString1 "Hello`r`nWorld" -InputString2 "Hello`nWorld" + # Returns: $true + +.EXAMPLE + Compare-StringRnLn -InputString1 "Line1`r`nLine2" -InputString2 "Line1`rLine2" + # Returns: $true + +.NOTES + This function ensures that strings with different line-ending formats are treated as equal if their content is otherwise identical. +#> function Compare-StringRnLn { param ( [string]$InputString1, @@ -58,7 +88,34 @@ function Compare-StringRnLn { return ($InputString1.Trim() -replace "`r`n|`n|`r", "`n") -eq ($InputString2.Trim() -replace "`r`n|`n|`r", "`n") } +<# +.SYNOPSIS + Converts a PSCustomObject into an ordered hashtable. + +.DESCRIPTION + This function recursively converts a PSCustomObject, including nested objects and collections, into an ordered hashtable. + It ensures that all properties are retained while maintaining their original structure. + +.PARAMETER InputObject + The PSCustomObject to be converted into an ordered hashtable. + +.OUTPUTS + [System.Collections.Specialized.OrderedDictionary] + Returns an ordered hashtable representation of the input PSCustomObject. +.EXAMPLE + $object = [PSCustomObject]@{ Name = "Pode"; Version = "2.0"; Config = [PSCustomObject]@{ Debug = $true } } + Convert-PsCustomObjectToOrderedHashtable -InputObject $object + # Returns: An ordered hashtable representation of $object. + +.EXAMPLE + $object = [PSCustomObject]@{ Users = @([PSCustomObject]@{ Name = "Alice" }, [PSCustomObject]@{ Name = "Bob" }) } + Convert-PsCustomObjectToOrderedHashtable -InputObject $object + # Returns: An ordered hashtable where 'Users' is an array of ordered hashtables. + +.NOTES + This function preserves key order and supports recursive conversion of nested objects and collections. +#> function Convert-PsCustomObjectToOrderedHashtable { [CmdletBinding()] param ( @@ -113,6 +170,37 @@ function Convert-PsCustomObjectToOrderedHashtable { } } +<# +.SYNOPSIS + Compares two hashtables to determine if they are equal. + +.DESCRIPTION + This function recursively compares two hashtables, checking whether they contain the same keys and values. + It also handles nested hashtables and arrays, ensuring deep comparison of all elements. + +.PARAMETER Hashtable1 + The first hashtable to compare. + +.PARAMETER Hashtable2 + The second hashtable to compare. + +.OUTPUTS + [bool] + Returns `$true` if both hashtables are equal, otherwise returns `$false`. + +.EXAMPLE + $hash1 = @{ Name = "Pode"; Version = "2.0"; Config = @{ Debug = $true } } + $hash2 = @{ Name = "Pode"; Version = "2.0"; Config = @{ Debug = $true } } + Compare-Hashtable -Hashtable1 $hash1 -Hashtable2 $hash2 + # Returns: $true + +.EXAMPLE + $hash1 = @{ Name = "Pode"; Version = "2.0" } + $hash2 = @{ Name = "Pode"; Version = "2.1" } + Compare-Hashtable -Hashtable1 $hash1 -Hashtable2 $hash2 + # Returns: $false + +#> function Compare-Hashtable { param ( [object]$Hashtable1, @@ -251,6 +339,7 @@ function Wait-ForWebServer { try { # Send a request but ignore status codes (any response means the server is online) $null = Invoke-WebRequest -Uri $Uri -UseBasicParsing -TimeoutSec 3 + Write-Host "Webserver is online at $Uri" return $true } catch { @@ -258,7 +347,7 @@ function Wait-ForWebServer { return $true } else { - Write-Debug "Waiting for webserver to come online at $Uri... (Attempt $($RetryCount+1)/$MaxRetries)" + Write-Host "Waiting for webserver to come online at $Uri... (Attempt $($RetryCount+1)/$MaxRetries)" } } From d845c13221bcdc01562bcc991c5ef054fbf52051 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sun, 2 Mar 2025 08:10:13 -0800 Subject: [PATCH 47/53] fix sample --- examples/Session-Data.ps1 | 84 +++++++++++++++++++++++++++++++++++++++ examples/sessiondata.ps1 | 43 -------------------- 2 files changed, 84 insertions(+), 43 deletions(-) create mode 100644 examples/Session-Data.ps1 delete mode 100644 examples/sessiondata.ps1 diff --git a/examples/Session-Data.ps1 b/examples/Session-Data.ps1 new file mode 100644 index 000000000..b2ce0bd3a --- /dev/null +++ b/examples/Session-Data.ps1 @@ -0,0 +1,84 @@ +<# +.SYNOPSIS + Demonstrates session management using Pode with basic authentication. + +.DESCRIPTION + This script sets up a Pode web server with a basic authentication endpoint. It tracks user sessions and + increments a session counter each time the endpoint is accessed. + +.EXAMPLE + To run the sample: ./SessionData.ps1 + $result=Invoke-WebRequest -Uri "http://localhost:8081/auth/basic" -Method Post -Headers @{ Authorization = 'Basic bW9ydHk6cGlja2xl' } + $session = ($result.Headers['pode.sid'] | Select-Object -First 1) + + $result = Invoke-WebRequest -Uri "$($Endpoint)/auth/basic" -Method Post -Headers @{ 'pode.sid' = $session } + $content = ($result.Content | ConvertFrom-Json) + $content.Result #should be 2 + + $result = Invoke-WebRequest -Uri "$($Endpoint)/auth/basic" -Method Post -Headers @{ 'pode.sid' = $session } + $content = ($result.Content | ConvertFrom-Json) + $content.Result #should be 3 and so on... + +.LINK + https://github.com/Badgerati/Pode/blob/develop/examples/SessionData.ps1 + +.NOTES + Author: Pode Team + License: MIT License +#> + +try { + # Determine the script directory path + $ScriptPath = (Split-Path -Parent -Path $MyInvocation.MyCommand.Path) + $podePath = Split-Path -Parent -Path $ScriptPath + + # Check if Pode is available from source; otherwise, load it from installed modules + if (Test-Path -Path "$($podePath)/src/Pode.psm1" -PathType Leaf) { + Import-Module "$($podePath)/src/Pode.psm1" -Force -ErrorAction Stop + } + else { + Import-Module -Name 'Pode' -MaximumVersion 2.99 -ErrorAction Stop + } +} +catch { throw } # Stop execution if Pode module fails to load + +# Start the Pode web server +Start-PodeServer -ScriptBlock { + + # Define an HTTP endpoint on localhost:8081 + Add-PodeEndpoint -Address localhost -Port 8081 -Protocol Http + + # Add a route to gracefully stop the server + Add-PodeRoute -Method Get -Path '/close' -ScriptBlock { + Close-PodeServer + } + + # Enable session middleware with secret-based authentication and session persistence + Enable-PodeSessionMiddleware -Secret 'schwifty' -Duration 5 -Extend -UseHeaders + + # Define a basic authentication scheme + New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'Auth' -ScriptBlock { + param($username, $password) + + # Authenticate user based on predefined credentials + if (($username -eq 'morty') -and ($password -eq 'pickle')) { + return @{ User = @{ ID = 'M0R7Y302' } } # Return user ID if authentication is successful + } + + return @{ Message = 'Invalid details supplied' } # Return error message for failed authentication + } + + # Define a route that requires authentication and maintains session state + Add-PodeRoute -Method Post -Path '/auth/basic' -Authentication Auth -ScriptBlock { + + # Increment session view count for the authenticated user + $WebEvent.Session.Data.Views++ + + # Return JSON response with session details + Write-PodeJsonResponse -Value @{ + Result = 'OK' + Username = $WebEvent.Auth.User.ID + Views = $WebEvent.Session.Data.Views + } + } +} diff --git a/examples/sessiondata.ps1 b/examples/sessiondata.ps1 deleted file mode 100644 index 323c5cd33..000000000 --- a/examples/sessiondata.ps1 +++ /dev/null @@ -1,43 +0,0 @@ -try { - # Determine the script path and Pode module path - $ScriptPath = (Split-Path -Parent -Path $MyInvocation.MyCommand.Path) - $podePath = Split-Path -Parent -Path $ScriptPath - - # Import the Pode module from the source path if it exists, otherwise from installed modules - if (Test-Path -Path "$($podePath)/src/Pode.psm1" -PathType Leaf) { - Import-Module "$($podePath)/src/Pode.psm1" -Force -ErrorAction Stop - } - else { - Import-Module -Name 'Pode' -MaximumVersion 2.99 -ErrorAction Stop - } -} -catch { throw } - -Start-PodeServer -ScriptBlock { - Add-PodeEndpoint -Address localhost -Port $Port -Protocol Http - Add-PodeRoute -Method Get -Path '/close' -ScriptBlock { - Close-PodeServer - } - - Enable-PodeSessionMiddleware -Secret 'schwifty' -Duration 5 -Extend -UseHeaders - - New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'Auth' -ScriptBlock { - param($username, $password) - - if (($username -eq 'morty') -and ($password -eq 'pickle')) { - return @{ User = @{ ID = 'M0R7Y302' } } - } - - return @{ Message = 'Invalid details supplied' } - } - - Add-PodeRoute -Method Post -Path '/auth/basic' -Authentication Auth -ScriptBlock { - $WebEvent.Session.Data.Views++ - - Write-PodeJsonResponse -Value @{ - Result = 'OK' - Username = $WebEvent.Auth.User.ID - Views = $WebEvent.Session.Data.Views - } - } -} \ No newline at end of file From 038acc93d52710f9eef20c4282459a8686858a87 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Mon, 3 Mar 2025 18:08:41 -0800 Subject: [PATCH 48/53] Update Context.ps1 --- src/Private/Context.ps1 | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Private/Context.ps1 b/src/Private/Context.ps1 index 9b0e76ea4..c82ba335f 100644 --- a/src/Private/Context.ps1 +++ b/src/Private/Context.ps1 @@ -153,9 +153,6 @@ function New-PodeContext { $ctx.Server.Console = $Console $ctx.Server.ComputerName = [System.Net.DNS]::GetHostName() - # set to True after the server is started - $ctx.Server.Started = $false - # list of created listeners/receivers $ctx.Listeners = @() $ctx.Receivers = @() From e7b0afcb6769827556b52f95fbd0a0b988d09e73 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sun, 9 Mar 2025 10:37:03 -0700 Subject: [PATCH 49/53] fix issue withs suspend --- examples/PetStore/Petstore-OpenApi.ps1 | 4 +- src/Pode.psd1 | 5 +- src/Private/FileWatchers.ps1 | 4 +- src/Private/Logging.ps1 | 115 +-------------------- src/Private/PodeServer.ps1 | 4 +- src/Private/SmtpServer.ps1 | 4 +- src/Private/TcpServer.ps1 | 4 +- src/Private/WebSockets.ps1 | 4 +- src/Public/Logging.ps1 | 135 +++++++++++++++++++++---- tests/unit/Logging.Tests.ps1 | 4 +- 10 files changed, 134 insertions(+), 149 deletions(-) diff --git a/examples/PetStore/Petstore-OpenApi.ps1 b/examples/PetStore/Petstore-OpenApi.ps1 index ed78f7f96..fe102e7cf 100644 --- a/examples/PetStore/Petstore-OpenApi.ps1 +++ b/examples/PetStore/Petstore-OpenApi.ps1 @@ -112,7 +112,9 @@ Start-PodeServer -Threads 1 -ScriptBlock { } # Enable error logging - New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging + New-PodeFileLoggingMethod -Name 'error' -MaxDays 4 -Format RFC5424 -ISO8601 | Enable-PodeErrorLogging + New-PodeFileLoggingMethod -Name 'petstore' -MaxDays 4 -Format RFC5424 -ISO8601 | Enable-PodeDefaultLogging -Levels Alert,Critical,Emergency,Error,Informational,Warning + New-PodeFileLoggingMethod -Name 'access' -MaxDays 4 -ISO8601 | Enable-PodeRequestLogging -LogFormat Extended # Configure CORS Set-PodeSecurityAccessControl -Origin '*' -Duration 7200 -WithOptions -AuthorizationHeader -autoMethods -AutoHeader -Credentials -CrossDomainXhrRequests diff --git a/src/Pode.psd1 b/src/Pode.psd1 index 10417be39..1fe7099e0 100644 --- a/src/Pode.psd1 +++ b/src/Pode.psd1 @@ -299,10 +299,10 @@ 'New-PodeGraylogLoggingMethod', 'New-PodeLogInsightLoggingMethod', 'New-PodeSplunkLoggingMethod', + 'Enable-PodeRequestLogging', 'Enable-PodeErrorLogging', 'Enable-PodeDefaultLogging', - 'Disable-PodeRequestLogging', 'Disable-PodeErrorLogging', 'Disable-PodeDefaultLogging', @@ -318,7 +318,8 @@ 'Enable-PodeLog', 'Disable-PodeLog', 'Clear-PodeLogging', - 'Get-PodeLoggingLevel', + 'Test-PodeLoggerEnabled', + 'Get-PodeLoggerLevel' # core 'Start-PodeServer', diff --git a/src/Private/FileWatchers.ps1 b/src/Private/FileWatchers.ps1 index 480a19dbf..3e1f1c0d1 100644 --- a/src/Private/FileWatchers.ps1 +++ b/src/Private/FileWatchers.ps1 @@ -12,8 +12,8 @@ function New-PodeFileWatcher { [OutputType([PodeWatcher])] param() $watcher = [PodeWatcher]::new($PodeContext.Tokens.Cancellation.Token) - $watcher.ErrorLoggingEnabled = (Test-PodeErrorLoggingEnabled) - $watcher.ErrorLoggingLevels = If ( $listener.ErrorLoggingEnabled) { @(Get-PodeErrorLoggingLevel) } else { @() } + $watcher.ErrorLoggingEnabled = (Test-PodeLoggerEnabled -Type Error) + $watcher.ErrorLoggingLevels = If ( $listener.ErrorLoggingEnabled) { @(Get-PodeLoggerLevel -Type Error) } else { @() } return $watcher } diff --git a/src/Private/Logging.ps1 b/src/Private/Logging.ps1 index 482c9f5a2..cf533ecb3 100644 --- a/src/Private/Logging.ps1 +++ b/src/Private/Logging.ps1 @@ -480,120 +480,7 @@ function Get-PodeLogger { } return $PodeContext.Server.Logging.Type[$Name] } - -<# -.SYNOPSIS -Tests if a specified logger is a standard logger. - -.DESCRIPTION -This function checks if the specified logger is configured as a standard logger in the Pode context. - -.PARAMETER Name -The name of the logger to test. - -.OUTPUTS -[bool] - Returns $true if the logger is a standard logger, otherwise $false. - -.EXAMPLE -Test-PodeStandardLogger -Name 'MyLogger' -#> -function Test-PodeStandardLogger { - [CmdletBinding()] - [OutputType([bool])] - param( - [Parameter(Mandatory = $true)] - [string] - $Name - ) - if (!$PodeContext.Server.Logging.Type.ContainsKey($Name)) { - throw $PodeLocale.loggerDoesNotExistExceptionMessage - } - # Check if the specified logger is a standard logger - return $PodeContext.Server.Logging.Type[$Name].Standard -} - -<# -.SYNOPSIS -Determines if a specified logger is enabled. - -.DESCRIPTION -This function checks if a specified logger is enabled by verifying if logging is enabled in the Pode context and if the logger exists within the logging configuration. - -.PARAMETER Name -The name of the logger to check. - -.EXAMPLE -Test-PodeLoggerEnabled -Name 'MyLogger' - -# This command checks if the logger named 'MyLogger' is enabled. -#> -function Test-PodeLoggerEnabled { - param( - [string] - $Name - ) - - if ($Name) { - # Check if logging is enabled and if the specified logger exists - return ([pode.PodeLogger]::Enabled -and $PodeContext -and $PodeContext.Server.Logging.Type.ContainsKey($Name)) - } - else { - # Check if logging is generally enabled - return [pode.PodeLogger]::Enabled - } -} - - -<# -.SYNOPSIS - Gets the error logging levels for Pode. - -.DESCRIPTION - This function retrieves the error logging levels configured for Pode. It returns an array of available error levels. - -.PARAMETER Name - The name of the Pode logger to retrieve. - -.OUTPUTS - An array of error logging levels. - -.NOTES - This is an internal function and may change in future releases of Pode. -#> -function Get-PodeErrorLoggingLevel { - return (Get-PodeLogger -Name ([Pode.PodeLogger]::ErrorLogName)).Arguments.Levels -} - -<# -.SYNOPSIS -Tests if error logging is enabled. - -.DESCRIPTION -This function checks if error logging is enabled by testing the logger configuration for error logging. - -.EXAMPLE -Test-PodeErrorLoggingEnabled -#> -function Test-PodeErrorLoggingEnabled { - # Get the name of the error logger and test if it is enabled - return (Test-PodeLoggerEnabled -Name ([Pode.PodeLogger]::ErrorLogName)) -} - -<# -.SYNOPSIS -Tests if request logging is enabled. - -.DESCRIPTION -This function checks if request logging is enabled by testing the logger configuration for request logging. - -.EXAMPLE -Test-PodeRequestLoggingEnabled -#> -function Test-PodeRequestLoggingEnabled { - # Get the name of the request logger and test if it is enabled - return (Test-PodeLoggerEnabled -Name ([Pode.PodeLogger]::RequestLogName)) -} - + <# .SYNOPSIS diff --git a/src/Private/PodeServer.ps1 b/src/Private/PodeServer.ps1 index 8bff9c498..e481f39ad 100644 --- a/src/Private/PodeServer.ps1 +++ b/src/Private/PodeServer.ps1 @@ -87,8 +87,8 @@ function Start-PodeWebServer { # Create the listener $listener = & $("New-Pode$($PodeContext.Server.ListenerType)Listener") -CancellationToken $PodeContext.Tokens.Cancellation.Token - $listener.ErrorLoggingEnabled = (Test-PodeErrorLoggingEnabled) - $listener.ErrorLoggingLevels = If ( $listener.ErrorLoggingEnabled) { @(Get-PodeErrorLoggingLevel) } else { @() } + $listener.ErrorLoggingEnabled = (Test-PodeLoggerEnabled -Type Error) + $listener.ErrorLoggingLevels = If ( $listener.ErrorLoggingEnabled) { @(Get-PodeLoggerLevel -Type Error) } else { @() } $listener.RequestTimeout = $PodeContext.Server.Request.Timeout $listener.RequestBodySize = $PodeContext.Server.Request.BodySize $listener.ShowServerDetails = [bool]$PodeContext.Server.Security.ServerDetails diff --git a/src/Private/SmtpServer.ps1 b/src/Private/SmtpServer.ps1 index a8becafab..eaef6a644 100644 --- a/src/Private/SmtpServer.ps1 +++ b/src/Private/SmtpServer.ps1 @@ -66,8 +66,8 @@ function Start-PodeSmtpServer { # create the listener $listener = [PodeListener]::new($PodeContext.Tokens.Cancellation.Token) - $listener.ErrorLoggingEnabled = (Test-PodeErrorLoggingEnabled) - $listener.ErrorLoggingLevels = If ( $listener.ErrorLoggingEnabled) { @(Get-PodeErrorLoggingLevel) } else { @() } + $listener.ErrorLoggingEnabled = (Test-PodeLoggerEnabled -Type Error) + $listener.ErrorLoggingLevels = If ( $listener.ErrorLoggingEnabled) { @(Get-PodeLoggerLevel -Type Error) } else { @() } $listener.RequestTimeout = $PodeContext.Server.Request.Timeout $listener.RequestBodySize = $PodeContext.Server.Request.BodySize diff --git a/src/Private/TcpServer.ps1 b/src/Private/TcpServer.ps1 index a5c63dd8c..89e1918d2 100644 --- a/src/Private/TcpServer.ps1 +++ b/src/Private/TcpServer.ps1 @@ -60,8 +60,8 @@ function Start-PodeTcpServer { # create the listener $listener = [PodeListener]::new($PodeContext.Tokens.Cancellation.Token) - $listener.ErrorLoggingEnabled = (Test-PodeErrorLoggingEnabled) - $listener.ErrorLoggingLevels = If ( $listener.ErrorLoggingEnabled) { @(Get-PodeErrorLoggingLevel) } else { @() } + $listener.ErrorLoggingEnabled = (Test-PodeLoggerEnabled -Type Error) + $listener.ErrorLoggingLevels = If ( $listener.ErrorLoggingEnabled) { @(Get-PodeLoggerLevel -Type Error) } else { @() } $listener.RequestTimeout = $PodeContext.Server.Request.Timeout $listener.RequestBodySize = $PodeContext.Server.Request.BodySize diff --git a/src/Private/WebSockets.ps1 b/src/Private/WebSockets.ps1 index fb1377ce6..19ae4445d 100644 --- a/src/Private/WebSockets.ps1 +++ b/src/Private/WebSockets.ps1 @@ -21,8 +21,8 @@ function New-PodeWebSocketReceiver { try { $receiver = [PodeReceiver]::new($PodeContext.Tokens.Cancellation.Token) - $receiver.ErrorLoggingEnabled = (Test-PodeErrorLoggingEnabled) - $receiver.ErrorLoggingLevels = If ( $listener.ErrorLoggingEnabled) { @(Get-PodeErrorLoggingLevel) } else { @() } + $receiver.ErrorLoggingEnabled = (Test-PodeLoggerEnabled -Type Error) + $receiver.ErrorLoggingLevels = If ( $listener.ErrorLoggingEnabled) { @(Get-PodeLoggerLevel -Type Error) } else { @() } $PodeContext.Server.WebSockets.Receiver = $receiver $PodeContext.Receivers += $receiver } diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 98741d1c0..8f9ede115 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -848,7 +848,7 @@ function Write-PodeErrorLog { $name = [Pode.PodeLogger]::ErrorLogName if (Test-PodeLoggerEnabled -Name $name) { Write-PodeLog @PSBoundParameters -name $name -SuppressErrorLog - if ($PodeContext.Server.Logging.Type[$name].DuplicateToDefaultLog -and ! $SuppressDefaultLog) { + if ((Test-PodeLoggerEnabled -Type Default) -and ($PodeContext.Server.Logging.Type[$name].DuplicateToDefaultLog) -and (! $SuppressDefaultLog) ) { Write-PodeLog @PSBoundParameters -name ([Pode.PodeLogger]::DefaultLogName) -SuppressErrorLog } } @@ -986,7 +986,7 @@ function Write-PodeLog { switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) { 'inputobject' { if (!$Level) { $Level = 'Informational' } # Default to Informational. - if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } # If the level is not configured, use the + if ( @(Get-PodeLoggerLevel -Name $Name) -inotcontains $Level) { return } # If the level is not configured, use the $logItem = @{ Name = $Name @@ -997,7 +997,7 @@ function Write-PodeLog { } 'message' { if (!$Level) { $Level = 'Informational' } # Default to Informational. - if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } # If the log level is not configured, return. + if ( @(Get-PodeLoggerLevel -Name $Name) -inotcontains $Level) { return } # If the log level is not configured, return. $logItem = @{ Name = $Name @@ -1012,7 +1012,7 @@ function Write-PodeLog { } 'exception' { if (!$Level) { $Level = 'Error' } # Default to Error. - if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } # If the level is not supported, return. + if ( @(Get-PodeLoggerLevel -Name $Name) -inotcontains $Level) { return } # If the level is not supported, return. $logItem = @{ Name = $Name @@ -1026,7 +1026,7 @@ function Write-PodeLog { } 'errorrecord' { if (!$Level) { $Level = 'Error' } # Default to Error. - if ( @(Get-PodeLoggingLevel -Name $Name) -inotcontains $Level) { return } # If the level is not supported, return. + if ( @(Get-PodeLoggerLevel -Name $Name) -inotcontains $Level) { return } # If the level is not supported, return. $logItem = @{ Name = $Name @@ -1050,7 +1050,7 @@ function Write-PodeLog { $logItem.Item.ThreadId = if ($ThreadId) { $ThreadId } else { [System.Threading.Thread]::CurrentThread.ManagedThreadId } # If error logging is not suppressed, log errors or exceptions. - if ((! $SuppressErrorLog.IsPresent) -and (Test-PodeErrorLoggingEnabled)) { + if ((! $SuppressErrorLog.IsPresent) -and (Test-PodeLoggerEnabled -Type Error)) { if ($PSCmdlet.ParameterSetName.ToLowerInvariant() -eq 'exception') { [Pode.PodeLogger]::Enqueue( @{ Name = [Pode.PodeLogger]::ErrorLogName @@ -1246,33 +1246,128 @@ function Clear-PodeLogging { [pode.PodeLogger]::Clear() } + <# .SYNOPSIS - Retrieves the logging levels for a specified Pode logger. +Determines if a specified logger or a predefined log type is enabled. .DESCRIPTION - The `Get-PodeLoggingLevel` function takes the name of a logger and returns its associated logging levels. This function verifies whether the logger exists before attempting to retrieve its levels. +This function checks if logging is enabled in Pode and verifies if the specified logger or a predefined log type +(Error, Default, Request) exists within the logging configuration. .PARAMETER Name - The name of the logger for which to retrieve the logging levels. This parameter is mandatory. +The name of the logger to check. If not specified, it checks if logging is generally enabled. + +.PARAMETER Type +The type of predefined logging to check. Accepted values: 'Error', 'Default', 'Request'. + +.EXAMPLE +Test-PodeLoggerEnabled -Name 'MyCustomLogger' +# Checks if the custom logger 'MyCustomLogger' is enabled. + +.EXAMPLE +Test-PodeLoggerEnabled -Type Error +# Checks if error logging is enabled. + +.EXAMPLE +Test-PodeLoggerEnabled -Type Default +# Checks if default logging is enabled. + +.EXAMPLE +Test-PodeLoggerEnabled -Type Request +# Checks if request logging is enabled. +#> +function Test-PodeLoggerEnabled { + param( + [Parameter(Position = 0, Mandatory = $false, ParameterSetName = 'ByName')] + [string]$Name, + + [Parameter(Position = 0, Mandatory = $false, ParameterSetName = 'ByType')] + [ValidateSet('Error', 'Default', 'Request')] + [string]$Type + ) + + # Ensure logging is enabled in Pode before checking specific loggers + if (![pode.PodeLogger]::Enabled -or ($null -eq $PodeContext)) { + return $false + } + + # Determine the logger name if using a predefined log type + if ($Type) { + $Name = switch ($Type) { + 'Error' { [Pode.PodeLogger]::ErrorLogName } + 'Default' { [Pode.PodeLogger]::DefaultLogName } + 'Request' { [Pode.PodeLogger]::RequestLogName } + } + } + + # If no name is provided, return whether logging is generally enabled + if ([string]::IsNullOrEmpty($Name)) { + return $true + } + + # Check if the specified logger exists in Pode's logging configuration + return $PodeContext.Server.Logging.Type.ContainsKey($Name) +} + +<# +.SYNOPSIS + Retrieves the logging levels for a specified logger or predefined log type in Pode. + +.DESCRIPTION + This function retrieves the logging levels configured for a specified Pode logger. + It supports both predefined log types ('Error', 'Default', 'Request') and custom logger names. + +.PARAMETER Name + The name of the logger to retrieve. + +.PARAMETER Type + The type of predefined logging to retrieve levels for. Accepted values: 'Error', 'Default', 'Request'. .OUTPUTS - An array of strings representing the logging levels of the specified Pode logger. If the logger does not exist, an empty array is returned. + An array of logging levels. + +.EXAMPLE + Get-PodeLoggerLevel -Type Error + # Retrieves the logging levels for error logging. + +.EXAMPLE + Get-PodeLoggerLevel -Type Default + # Retrieves the logging levels for default logging. .EXAMPLE - Get-PodeLoggingLevel -Name 'FileLogger' + Get-PodeLoggerLevel -Type Request + # Retrieves the logging levels for request logging. - This command retrieves the logging levels for the logger named 'FileLogger'. +.EXAMPLE + Get-PodeLoggerLevel -Name 'MyCustomLogger' + # Retrieves the logging levels for a custom logger named 'MyCustomLogger'. #> -function Get-PodeLoggingLevel { +function Get-PodeLoggerLevel { param( - [Parameter(Mandatory = $true)] - [string] - $Name + [Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'ByName')] + [string]$Name, + + [Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'ByType')] + [ValidateSet('Error', 'Default', 'Request')] + [string]$Type ) - if (Test-PodeLoggerEnabled -Name $Name) { - return (Get-PodeLogger -Name $Name).Arguments.Levels + # Determine the logger name if using a predefined log type + if ($Type) { + $Name = switch ($Type) { + 'Error' { [Pode.PodeLogger]::ErrorLogName } + 'Default' { [Pode.PodeLogger]::DefaultLogName } + 'Request' { [Pode.PodeLogger]::RequestLogName } + } + } + + # Ensure the logger is enabled before retrieving levels + if (!(Test-PodeLoggerEnabled -Name $Name)) { + return @() } - return @() -} \ No newline at end of file + + # Retrieve the logger and return its levels + $Logger = Get-PodeLogger -Name $Name + return $Logger.Arguments.Levels +} diff --git a/tests/unit/Logging.Tests.ps1 b/tests/unit/Logging.Tests.ps1 index 4e6e62302..3dfa8734f 100644 --- a/tests/unit/Logging.Tests.ps1 +++ b/tests/unit/Logging.Tests.ps1 @@ -77,7 +77,7 @@ Describe 'Write-PodeLog' { It 'Adds a log item' { Mock Test-PodeLoggerEnabled { return $true } - Mock Get-PodeLoggingLevel { return @('Informational') } + Mock Get-PodeLoggerLevel { return @('Informational') } Write-PodeLog -Name 'test' -InputObject 'test' [Pode.PodeLogger]::Count | Should -Be 1 @@ -96,7 +96,7 @@ Describe 'Write-PodeErrorLog' { ([Pode.PodeLogger]::ErrorLogName) = @{ Standard = $false } - test = @{ + test = @{ Standard = $false } } From 930cb630b619970b457a71b5f144d1742151325b Mon Sep 17 00:00:00 2001 From: mdaneri Date: Tue, 11 Mar 2025 14:15:20 -0700 Subject: [PATCH 50/53] Fix issue with Write-PodeLog when inputobject is a PSCustomObject --- src/Private/Helpers.ps1 | 98 ++++++++++++++++++++++++++++++++++++++++- src/Public/Logging.ps1 | 11 +++-- 2 files changed, 105 insertions(+), 4 deletions(-) diff --git a/src/Private/Helpers.ps1 b/src/Private/Helpers.ps1 index 8588686c8..bfd5303f1 100644 --- a/src/Private/Helpers.ps1 +++ b/src/Private/Helpers.ps1 @@ -593,7 +593,7 @@ function Close-PodeServerInternal { try { #Disable Logging before closing Disable-PodeLog - + # ensure the token is cancelled Write-Verbose 'Cancelling main cancellation token' Close-PodeCancellationTokenRequest -Type Cancellation, Terminate @@ -3969,3 +3969,99 @@ function ConvertTo-PodeSleep { function Test-PodeIsISEHost { return ((Test-PodeIsWindows) -and ('Windows PowerShell ISE Host' -eq $Host.Name)) } + + +<# +.SYNOPSIS + Converts a PSCustomObject to an ordered hashtable. + +.DESCRIPTION + This function recursively converts a given PSCustomObject into an ordered hashtable. + It ensures that properties maintain their order and that nested PSCustomObjects and + collections are also converted appropriately. + +.PARAMETER InputObject + Specifies the PSCustomObject to be converted into an ordered hashtable. + This parameter is mandatory and accepts pipeline input. + +.OUTPUTS + [System.Collections.Specialized.OrderedDictionary] + Returns an ordered hashtable representing the original PSCustomObject. + +.EXAMPLE + $object = [PSCustomObject]@{ + Name = "John" + Age = 30 + Address = [PSCustomObject]@{ + City = "New York" + State = "NY" + } + } + + Convert-PsCustomObjectToOrderedHashtable -InputObject $object + + This example converts the PSCustomObject `$object` into an ordered hashtable. + +.EXAMPLE + $object | Convert-PsCustomObjectToOrderedHashtable + + This example demonstrates using the function with pipeline input. + +.NOTES + Internal helper function `Convert-ObjectRecursively` is used to process nested objects + and collections recursively. +#> + +function Convert-PsCustomObjectToOrderedHashtable { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSCustomObject]$InputObject + ) + begin { + # Define a recursive function within the process block + function Convert-ObjectRecursively { + param ( + [Parameter(Mandatory = $true)] + [System.Object] + $InputObject + ) + + # Initialize an ordered dictionary + $orderedHashtable = [ordered]@{} + + # Loop through each property of the PSCustomObject + foreach ($property in $InputObject.PSObject.Properties) { + # Check if the property value is a PSCustomObject + if ($property.Value -is [PSCustomObject]) { + # Recursively convert the nested PSCustomObject + $orderedHashtable[$property.Name] = Convert-ObjectRecursively -InputObject $property.Value + } + elseif ($property.Value -is [System.Collections.IEnumerable] -and -not ($property.Value -is [string])) { + # If the value is a collection, check each element + $convertedCollection = @() + foreach ($item in $property.Value) { + if ($item -is [PSCustomObject]) { + $convertedCollection += Convert-ObjectRecursively -InputObject $item + } + else { + $convertedCollection += $item + } + } + $orderedHashtable[$property.Name] = $convertedCollection + } + else { + # Add the property name and value to the ordered hashtable + $orderedHashtable[$property.Name] = $property.Value + } + } + + # Return the resulting ordered hashtable + return $orderedHashtable + } + } + process { + # Call the recursive helper function for each input object + Convert-ObjectRecursively -InputObject $InputObject + } +} \ No newline at end of file diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 8f9ede115..82ac174f5 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -990,8 +990,13 @@ function Write-PodeLog { $logItem = @{ Name = $Name - Item = $InputObject - Level = $Level + Level = $Levelf + } + $logItem.Item = if ($InputObject -is [PSCustomObject]) { + Convert-PsCustomObjectToOrderedHashtable -InputObject $InputObject + } + else { + $InputObject } break } @@ -1246,7 +1251,7 @@ function Clear-PodeLogging { [pode.PodeLogger]::Clear() } - + <# .SYNOPSIS Determines if a specified logger or a predefined log type is enabled. From 21a2cc0908ed41ad959b04190f2d175cc8c78ce7 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Tue, 11 Mar 2025 16:09:58 -0700 Subject: [PATCH 51/53] Update Logging.ps1 --- src/Public/Logging.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index 82ac174f5..dbfb7cc9d 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -992,7 +992,7 @@ function Write-PodeLog { Name = $Name Level = $Levelf } - $logItem.Item = if ($InputObject -is [PSCustomObject]) { + $logItem.Item = if ( $InputObject.PSObject.TypeNames -contains 'System.Management.Automation.PSCustomObject') { Convert-PsCustomObjectToOrderedHashtable -InputObject $InputObject } else { From e1b9a3ef5578cfa6833cabd7634dc3cbde792ba2 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sat, 19 Apr 2025 09:00:45 -0700 Subject: [PATCH 52/53] merge fixes --- src/Listener/PodeHelpers.cs | 24 ------------------------ src/Listener/PodeListener.cs | 4 ++-- src/Listener/PodeLogger.cs | 6 +++--- src/Listener/PodeSignalRequest.cs | 2 +- 4 files changed, 6 insertions(+), 30 deletions(-) diff --git a/src/Listener/PodeHelpers.cs b/src/Listener/PodeHelpers.cs index 4ad5d7eba..1069ca8ee 100644 --- a/src/Listener/PodeHelpers.cs +++ b/src/Listener/PodeHelpers.cs @@ -44,30 +44,6 @@ public static bool IsNetFramework } } - public static void WriteException(Exception ex, PodeConnector connector = default, PodeLoggingLevel level = PodeLoggingLevel.Error) - { - if (ex == default(Exception)) - { - return; - } - - // return if logging disabled, or if level isn't being logged - if (connector != default(PodeConnector) && (!connector.ErrorLoggingEnabled || !connector.ErrorLoggingLevels.Contains(level.ToString(), StringComparer.InvariantCultureIgnoreCase))) - { - return; - } - - // write the exception to terminal - Console.WriteLine($"[{level}] {ex.GetType().Name}: {ex.Message}"); - Console.WriteLine(string.IsNullOrEmpty(ex.StackTrace) ? " [No Stack Trace]" : ex.StackTrace); - - if (ex.InnerException != null) - { - Console.WriteLine($"[{level}] {ex.InnerException.GetType().Name}: {ex.InnerException.Message}"); - Console.WriteLine(string.IsNullOrEmpty(ex.InnerException.StackTrace) ? " [No Stack Trace]" : ex.InnerException.StackTrace); - } - } - public static void HandleAggregateException(AggregateException aex, PodeConnector connector = default, PodeLoggingLevel level = PodeLoggingLevel.Error, bool handled = false) { try diff --git a/src/Listener/PodeListener.cs b/src/Listener/PodeListener.cs index 33bcd98a1..bb7271912 100644 --- a/src/Listener/PodeListener.cs +++ b/src/Listener/PodeListener.cs @@ -300,14 +300,14 @@ protected override void Close() PodeLogger.LogMessage($"Closed server events", this, PodeLoggingLevel.Verbose); // shutdown the sockets - PodeHelpers.WriteErrorMessage($"Closing sockets", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closing sockets", this, PodeLoggingLevel.Verbose); for (var i = Sockets.Count - 1; i >= 0; i--) { Sockets[i].Dispose(); } Sockets.Clear(); - PodeHelpers.WriteErrorMessage($"Closed sockets", this, PodeLoggingLevel.Verbose); + PodeLogger.LogMessage($"Closed sockets", this, PodeLoggingLevel.Verbose); } } } \ No newline at end of file diff --git a/src/Listener/PodeLogger.cs b/src/Listener/PodeLogger.cs index c2aa96d34..09483cf4b 100644 --- a/src/Listener/PodeLogger.cs +++ b/src/Listener/PodeLogger.cs @@ -124,7 +124,7 @@ public static void Clear() /// The logging level (default is Error). public static void LogException(Exception ex, PodeConnector connector = default(PodeConnector), PodeLoggingLevel level = PodeLoggingLevel.Error) { - if (ex == null) + if (ex == default(Exception)) { return; } @@ -139,12 +139,12 @@ public static void Clear() if (Terminal) { Console.WriteLine($"[{level}] {ex.GetType().Name}: {ex.Message}"); - Console.WriteLine(ex.StackTrace); + Console.WriteLine(string.IsNullOrEmpty(ex.StackTrace) ? " [No Stack Trace]" : ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine($"[{level}] {ex.InnerException.GetType().Name}: {ex.InnerException.Message}"); - Console.WriteLine(ex.InnerException.StackTrace); + Console.WriteLine(string.IsNullOrEmpty(ex.InnerException.StackTrace) ? " [No Stack Trace]" : ex.InnerException.StackTrace); } } diff --git a/src/Listener/PodeSignalRequest.cs b/src/Listener/PodeSignalRequest.cs index 4998cac00..8265d3c64 100644 --- a/src/Listener/PodeSignalRequest.cs +++ b/src/Listener/PodeSignalRequest.cs @@ -184,7 +184,7 @@ protected override async Task Parse(byte[] bytes, CancellationToken cancel catch (Exception ex) { // Log the error and return false to indicate failure. - PodeHelpers.WriteErrorMessage($"Error decoding WebSocket frame: {ex.Message}", Context.Listener, PodeLoggingLevel.Error, Context); + PodeLogger.LogMessage($"Error decoding WebSocket frame: {ex.Message}", Context.Listener, PodeLoggingLevel.Error, Context); throw; } finally From bf03196ed4b8ef366596377ce6c4f803d4d4d877 Mon Sep 17 00:00:00 2001 From: mdaneri Date: Sun, 18 May 2025 08:43:55 -0700 Subject: [PATCH 53/53] fix an issue with log exception --- examples/Logging.ps1 | 3 +-- src/Public/Logging.ps1 | 32 +++++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/examples/Logging.ps1 b/examples/Logging.ps1 index 979c16ce9..fb7f0cf9a 100644 --- a/examples/Logging.ps1 +++ b/examples/Logging.ps1 @@ -114,13 +114,12 @@ Start-PodeServer -browse { # GET request throws fake "500" server error status code Add-PodeRoute -Method Get -Path '/error' -ScriptBlock { - Disable-PodeRequestLogging Set-PodeResponseStatus -Code 500 } Add-PodeRoute -Method Get -Path '/exception' -ScriptBlock { try { - throw 'something happened' + throw 4 / 0 } catch { $_ | Write-PodeErrorLog diff --git a/src/Public/Logging.ps1 b/src/Public/Logging.ps1 index dbfb7cc9d..7794501ef 100644 --- a/src/Public/Logging.ps1 +++ b/src/Public/Logging.ps1 @@ -1007,10 +1007,13 @@ function Write-PodeLog { $logItem = @{ Name = $Name Item = @{ + Server = $PodeContext.Server.ComputerName Category = $Category.ToString() Level = $Level + Date = if ($log.Method.Arguments.AsUTC) { [datetime]::UtcNow } else { [datetime]::Now } Message = $Message Tag = $Tag + ThreadId =if($ThreadId) { $ThreadId } else { [System.Threading.Thread]::CurrentThread.ManagedThreadId } } } break @@ -1022,11 +1025,17 @@ function Write-PodeLog { $logItem = @{ Name = $Name Item = @{ - Level = $Level - Message = $Exception.Message - Tag = $Tag + Server = $PodeContext.Server.ComputerName + Level = $Level + Date = if ($log.Method.Arguments.AsUTC) { [datetime]::UtcNow } else { [datetime]::Now } + Category = $Exception.Source + Message = $Exception.Message + StackTrace = $Exception.StackTrace + Tag = $Tag + ThreadId =if($ThreadId) { $ThreadId } else { [System.Threading.Thread]::CurrentThread.ManagedThreadId } } } + break } 'errorrecord' { @@ -1036,9 +1045,14 @@ function Write-PodeLog { $logItem = @{ Name = $Name Item = @{ - Level = $Level - Message = $ErrorRecord.Exception.Message - Tag = $Tag + Server = $PodeContext.Server.ComputerName + Level = $Level + Date = if ($log.Method.Arguments.AsUTC) { [datetime]::UtcNow } else { [datetime]::Now } + Category = $ErrorRecord.CategoryInfo.ToString() + Message = $ErrorRecord.Exception.Message + StackTrace = $ErrorRecord.ScriptStackTrace + Tag = $Tag + ThreadId =if($ThreadId) { $ThreadId } else { [System.Threading.Thread]::CurrentThread.ManagedThreadId } } } break @@ -1046,13 +1060,13 @@ function Write-PodeLog { } if ($log.Standard) { # Add server details to the log item. - $logItem.Item.Server = $PodeContext.Server.ComputerName + # $logItem.Item.Server = $PodeContext.Server.ComputerName # Add the current date and time (UTC or local) to the log item. - $logItem.Item.Date = if ($log.Method.Arguments.AsUTC) { [datetime]::UtcNow } else { [datetime]::Now } + # $logItem.Item.Date = if ($log.Method.Arguments.AsUTC) { [datetime]::UtcNow } else { [datetime]::Now } # Set the thread ID if provided, otherwise use the current thread ID. - $logItem.Item.ThreadId = if ($ThreadId) { $ThreadId } else { [System.Threading.Thread]::CurrentThread.ManagedThreadId } + # $logItem.Item.ThreadId = if ($ThreadId) { $ThreadId } else { [System.Threading.Thread]::CurrentThread.ManagedThreadId } # If error logging is not suppressed, log errors or exceptions. if ((! $SuppressErrorLog.IsPresent) -and (Test-PodeLoggerEnabled -Type Error)) {