diff --git a/source/Stargate-API-Skeleton-Tests/StargateApplicationTest.class.st b/source/Stargate-API-Skeleton-Tests/StargateApplicationTest.class.st index 0672be2..62d827c 100644 --- a/source/Stargate-API-Skeleton-Tests/StargateApplicationTest.class.st +++ b/source/Stargate-API-Skeleton-Tests/StargateApplicationTest.class.st @@ -6,7 +6,9 @@ Class { #superclass : 'LaunchpadTest', #instVars : [ 'port', - 'baseUrl' + 'baseUrl', + 'logger', + 'logHTTPRequests' ], #category : 'Stargate-API-Skeleton-Tests', #package : 'Stargate-API-Skeleton-Tests' @@ -18,6 +20,14 @@ StargateApplicationTest class >> defaultTimeLimit [ ^5 minute ] +{ #category : 'private' } +StargateApplicationTest >> assert: aString isExpectedIncomingSummaryWith: aRequestShortDescription [ + + self assert: ( aString matchesRegex: + ( '\d{4}-\d{2}-\d{2} \d{2}\:\d{2}\:\d{2} ((\d+) (\d+))? Request Handled a\s{0,1}ZnRequest\(<1s>\) (\d+)ms' + expandMacrosWith: aRequestShortDescription ) ) +] + { #category : 'private' } StargateApplicationTest >> assert: string isLineEndingInsensitiveEqualsTo: anotherString [ @@ -85,8 +95,11 @@ StargateApplicationTest >> secret [ StargateApplicationTest >> setUp [ super setUp. + logger := MemoryLogger new. port := self freeListeningTCPPort. - StargateApplication logsDirectory ensureCreateDirectory + StargateApplication logsDirectory ensureCreateDirectory. + + logHTTPRequests := false ] { #category : 'private' } @@ -103,11 +116,14 @@ StargateApplicationTest >> startConcurrentConnectionsApp [ { #category : 'private' } StargateApplicationTest >> startPetStore [ - self start: PetStoreApplication withAll: { - '--pet-store.stargate.public-url=http://localhost:<1p>' expandMacrosWith: port. - '--pet-store.stargate.port=<1p>' expandMacrosWith: port. - '--pet-store.stargate.operations-secret=<1s>' expandMacrosWith: self secret }. - baseUrl := runningApplication configuration petStore stargate publicURL + self + start: PetStoreApplication + withAll: { ( '--pet-store.stargate.public-url=http://localhost:<1p>' expandMacrosWith: port ) . + ( '--pet-store.stargate.port=<1p>' expandMacrosWith: port ) . + ( '--pet-store.stargate.operations-secret=<1s>' expandMacrosWith: self secret ) . + ( '--pet-store.stargate.log-http-requests=<1p>' expandMacrosWith: + logHTTPRequests ) }. + baseUrl := runningApplication configuration petStore stargate publicURL ] { #category : 'private' } @@ -264,6 +280,78 @@ StargateApplicationTest >> testGetPets [ assert: json links size equals: 1 ] +{ #category : 'tests - logs' } +StargateApplicationTest >> testLogRequestsDuringSuccessfulGet [ + + | logRecord | + logger runDuring: [ + logHTTPRequests := true. + self testGetPets + ]. + + logRecord := NeoJSONObject fromString: ( String streamContents: [ :stream | + ( logger recordings at: logger recordings size - 1 ) printOneLineJsonOn: + stream ] ). + + self + assert: logRecord level equals: 'DEBUG'; + assert: logRecord message equals: 'Incoming HTTP request responded'; + assert: logRecord process equals: 'ZnManagingMultiThreadedServer HTTP worker'; + assert: logRecord summary isExpectedIncomingSummaryWith: 'GET \/pets'; + assert: logRecord request method equals: 'GET'; + assert: logRecord request uri equals: ( 'http://localhost:<1p>/pets' expandMacrosWith: port ); + assert: logRecord response code equals: 200; + assert: logRecord response totalSize equals: 59 +] + +{ #category : 'tests - logs' } +StargateApplicationTest >> testLogRequestsDuringSucessfulPost [ + + | logRecord | + logger runDuring: [ + logHTTPRequests := true. + self testCreatePet + ]. + + logRecord := NeoJSONObject fromString: ( String streamContents: [ :stream | + ( logger recordings at: logger recordings size - 1 ) printOneLineJsonOn: + stream ] ). + + self + assert: logRecord level equals: 'DEBUG'; + assert: logRecord message equals: 'Incoming HTTP request responded'; + assert: logRecord process equals: 'ZnManagingMultiThreadedServer HTTP worker'; + assert: logRecord summary isExpectedIncomingSummaryWith: 'POST \/pets'; + assert: logRecord request method equals: 'POST'; + assert: logRecord request uri equals: ( 'http://localhost:<1p>/pets' expandMacrosWith: port ); + assert: logRecord response code equals: 201; + assert: logRecord response totalSize equals: 96 +] + +{ #category : 'tests - logs' } +StargateApplicationTest >> testLogRequestsDuringUnsucessfulPost [ + + | logRecord | + logger runDuring: [ + logHTTPRequests := true. + self testUnsupportedMediaType + ]. + + logRecord := NeoJSONObject fromString: ( String streamContents: [ :stream | + ( logger recordings at: logger recordings size - 1 ) printOneLineJsonOn: + stream ] ). + + self + assert: logRecord level equals: 'DEBUG'; + assert: logRecord message equals: 'Incoming HTTP request responded'; + assert: logRecord process equals: 'ZnManagingMultiThreadedServer HTTP worker'; + assert: logRecord summary isExpectedIncomingSummaryWith: 'POST \/pets'; + assert: logRecord request method equals: 'POST'; + assert: logRecord request uri equals: ( 'http://localhost:<1p>/pets' expandMacrosWith: port ); + assert: logRecord response code equals: 415; + assert: logRecord response totalSize equals: 72 +] + { #category : 'tests - api' } StargateApplicationTest >> testMethodNotAllowed [ @@ -320,7 +408,7 @@ StargateApplicationTest >> testPrintHelpOn [ self assert: help isLineEndingInsensitiveEqualsTo: ('NAME pet-store [<1s>] - A RESTful API for Pet stores SYNOPSYS - pet-store --pet-store.stargate.public-url=% --pet-store.stargate.port=% --pet-store.stargate.operations-secret=% [--pet-store.stargate.concurrent-connections-threshold=%] + pet-store --pet-store.stargate.public-url=% --pet-store.stargate.port=% --pet-store.stargate.operations-secret=% [--pet-store.stargate.log-http-requests=%] [--pet-store.stargate.concurrent-connections-threshold=%] PARAMETERS --pet-store.stargate.public-url=% Public URL where the API is deployed. Used to create hypermedia links. @@ -328,6 +416,8 @@ PARAMETERS Listening port. --pet-store.stargate.operations-secret=% Secret key for checking JWT signatures. + --pet-store.stargate.log-http-requests=% + Boolean that indicates whether to log all the incoming HTTP requests. Defaults to false. --pet-store.stargate.concurrent-connections-threshold=% Set the maximum number of concurrent connections that I will accept. When this threshold is reached, a 503 Service Unavailable response will be sent and the connection will be closed. Defaults to 32. ENVIRONMENT @@ -337,6 +427,8 @@ ENVIRONMENT Listening port. PET_STORE__STARGATE__OPERATIONS_SECRET Secret key for checking JWT signatures. + PET_STORE__STARGATE__LOG_HTTP_REQUESTS + Boolean that indicates whether to log all the incoming HTTP requests. Defaults to false. PET_STORE__STARGATE__CONCURRENT_CONNECTIONS_THRESHOLD Set the maximum number of concurrent connections that I will accept. When this threshold is reached, a 503 Service Unavailable response will be sent and the connection will be closed. Defaults to 32. ' expandMacrosWith: PetStoreApplication version) diff --git a/source/Stargate-API-Skeleton/StargateApplication.class.st b/source/Stargate-API-Skeleton/StargateApplication.class.st index 2faa02f..dac1502 100644 --- a/source/Stargate-API-Skeleton/StargateApplication.class.st +++ b/source/Stargate-API-Skeleton/StargateApplication.class.st @@ -2,7 +2,8 @@ Class { #name : 'StargateApplication', #superclass : 'LaunchpadApplication', #instVars : [ - 'apiOptional' + 'apiOptional', + 'znEventToLogRecordAdapter' ], #classInstVars : [ 'Version' @@ -87,29 +88,36 @@ StargateApplication class >> stackTraceDumpExtension [ { #category : 'private' } StargateApplication class >> stargateConfigurationParameters [ - ^ Array - with: ( MandatoryConfigurationParameter - named: 'Public URL' - describedBy: 'Public URL where the API is deployed. Used to create hypermedia links' - inside: self sectionsForStargateConfiguration - convertingWith: #asUrl ) - with: ( MandatoryConfigurationParameter - named: 'Port' - describedBy: 'Listening port' - inside: self sectionsForStargateConfiguration - convertingWith: #asNumber ) - with: ( MandatoryConfigurationParameter - named: 'Operations Secret' - describedBy: 'Secret key for checking JWT signatures' - inside: self sectionsForStargateConfiguration - convertingWith: #asByteArray ) asSensitive - with: ( OptionalConfigurationParameter - named: 'Concurrent Connections Threshold' - describedBy: - 'Set the maximum number of concurrent connections that I will accept. When this threshold is reached, a 503 Service Unavailable response will be sent and the connection will be closed' - inside: self sectionsForStargateConfiguration - defaultingTo: 32 - convertingWith: #asNumber ) + ^ OrderedCollection new + add: ( MandatoryConfigurationParameter + named: 'Public URL' + describedBy: 'Public URL where the API is deployed. Used to create hypermedia links' + inside: self sectionsForStargateConfiguration + convertingWith: #asUrl ); + add: ( MandatoryConfigurationParameter + named: 'Port' + describedBy: 'Listening port' + inside: self sectionsForStargateConfiguration + convertingWith: #asNumber ); + add: ( MandatoryConfigurationParameter + named: 'Operations Secret' + describedBy: 'Secret key for checking JWT signatures' + inside: self sectionsForStargateConfiguration + convertingWith: #asByteArray ) asSensitive; + add: ( OptionalConfigurationParameter + named: 'Log HTTP Requests' + describedBy: 'Boolean that indicates whether to log all the incoming HTTP requests' + inside: self sectionsForStargateConfiguration + defaultingTo: false + convertingWith: #asBoolean ); + add: ( OptionalConfigurationParameter + named: 'Concurrent Connections Threshold' + describedBy: + 'Set the maximum number of concurrent connections that I will accept. When this threshold is reached, a 503 Service Unavailable response will be sent and the connection will be closed' + inside: self sectionsForStargateConfiguration + defaultingTo: 32 + convertingWith: #asNumber ); + yourself ] { #category : 'accessing' } @@ -177,8 +185,9 @@ StargateApplication >> authAlgorithm [ StargateApplication >> basicStartWithin: context [ | api | - + self logAPIVersion. + self configureHTTPRequestsLogging. api := self createAPI. self configureGlobalErrorHandlerIn: api; @@ -190,6 +199,7 @@ StargateApplication >> basicStartWithin: context [ StargateApplication >> basicStop [ apiOptional withContentDo: [ :api | api stop ]. + znEventToLogRecordAdapter stopListeners. super basicStop ] @@ -224,10 +234,12 @@ StargateApplication >> configureGlobalErrorHandlerIn: api [ ] unless: self isDebugModeEnabled ] -{ #category : 'private - accessing' } -StargateApplication >> controllersToInstall [ +{ #category : 'private - activation/deactivation' } +StargateApplication >> configureHTTPRequestsLogging [ - ^ self subclassResponsibility + znEventToLogRecordAdapter + logIncomingRequests: self stargateConfiguration logHTTPRequests; + startUpListeners ] { #category : 'private - activation/deactivation' } @@ -245,7 +257,8 @@ StargateApplication >> createAPI [ StargateApplication >> initialize [ super initialize. - apiOptional := Optional unused + apiOptional := Optional unused. + znEventToLogRecordAdapter := ZnEventToLogRecordAdapter new ] { #category : 'private - activation/deactivation' }