Skip to content

Commit

Permalink
Add support to log incoming and outgoing http requests
Browse files Browse the repository at this point in the history
  • Loading branch information
jvanecek committed Jul 2, 2024
1 parent d4eef66 commit 28a603f
Show file tree
Hide file tree
Showing 2 changed files with 237 additions and 37 deletions.
196 changes: 188 additions & 8 deletions source/Stargate-API-Skeleton-Tests/StargateApplicationTest.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ Class {
#superclass : 'LaunchpadTest',
#instVars : [
'port',
'baseUrl'
'baseUrl',
'logIncomingHTTPRequests',
'logOutgoingHTTPRequests',
'logger'
],
#category : 'Stargate-API-Skeleton-Tests',
#package : 'Stargate-API-Skeleton-Tests'
Expand All @@ -18,6 +21,22 @@ 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: aString isExpectedOutgoingSummaryWith: aRequestShortDescription [

self assert: ( aString matchesRegex:
( '\d{4}-\d{2}-\d{2} \d{2}\:\d{2}\:\d{2} ((\d+) (\d+))? <1s> (\d+) (\d+)B (\d+)ms'
expandMacrosWith: aRequestShortDescription ) )
]

{ #category : 'private' }
StargateApplicationTest >> assert: string isLineEndingInsensitiveEqualsTo: anotherString [

Expand Down Expand Up @@ -85,8 +104,12 @@ StargateApplicationTest >> secret [
StargateApplicationTest >> setUp [

super setUp.
logger := MemoryLogger new.
port := self freeListeningTCPPort.
StargateApplication logsDirectory ensureCreateDirectory
StargateApplication logsDirectory ensureCreateDirectory.

logIncomingHTTPRequests := false.
logOutgoingHTTPRequests := false.
]

{ #category : 'private' }
Expand All @@ -103,11 +126,16 @@ 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-incoming-http-requests=<1p>' expandMacrosWith:
logIncomingHTTPRequests ) .
( '--pet-store.stargate.log-outgoing-http-requests=<1p>' expandMacrosWith:
logOutgoingHTTPRequests ) }.
baseUrl := runningApplication configuration petStore stargate publicURL
]

{ #category : 'private' }
Expand Down Expand Up @@ -264,6 +292,150 @@ StargateApplicationTest >> testGetPets [
assert: json links size equals: 1
]

{ #category : 'tests - logs' }
StargateApplicationTest >> testLogIncomingRequestsDuringSuccessfulGet [

| logRecord |
logger runDuring: [
logIncomingHTTPRequests := 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 >> testLogIncomingRequestsDuringSucessfulPost [

| logRecord |
logger runDuring: [
logIncomingHTTPRequests := 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 >> testLogIncomingRequestsDuringUnsucessfulPost [

| logRecord |
logger runDuring: [
logIncomingHTTPRequests := 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 - logs' }
StargateApplicationTest >> testLogOutgoingRequestsDuringSucessfulGet [

| logRecord |
logger runDuring: [
logOutgoingHTTPRequests := true.
self testGetPets
].

logRecord := NeoJSONObject fromString:
( String streamContents: [ :stream |
logger recordings last printOneLineJsonOn: stream ] ).

self
assert: logRecord level equals: 'DEBUG';
assert: logRecord message equals: 'Outgoing HTTP request responded';
assert: logRecord process equals: 'Launchpad CLI';
assert: logRecord summary isExpectedOutgoingSummaryWith: '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 >> testLogOutgoingRequestsDuringSucessfulPost [

| logRecord |
logger runDuring: [
logOutgoingHTTPRequests := true.
self testCreatePet
].

logRecord := NeoJSONObject fromString:
( String streamContents: [ :stream |
logger recordings last printOneLineJsonOn: stream ] ).

self
assert: logRecord level equals: 'DEBUG';
assert: logRecord message equals: 'Outgoing HTTP request responded';
assert: logRecord process equals: 'Launchpad CLI';
assert: logRecord summary isExpectedOutgoingSummaryWith: '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 >> testLogOutgoingRequestsDuringUnsucessfulPost [

| logRecord |
logger runDuring: [
logOutgoingHTTPRequests := true.
self testUnsupportedMediaType
].

logRecord := NeoJSONObject fromString:
( String streamContents: [ :stream |
logger recordings last printOneLineJsonOn: stream ] ).

self
assert: logRecord level equals: 'DEBUG';
assert: logRecord message equals: 'Outgoing HTTP request responded';
assert: logRecord process equals: 'Launchpad CLI';
assert: logRecord summary isExpectedOutgoingSummaryWith: '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 [

Expand Down Expand Up @@ -320,14 +492,18 @@ StargateApplicationTest >> testPrintHelpOn [
self assert: help isLineEndingInsensitiveEqualsTo: ('NAME
pet-store [<1s>] - A RESTful API for Pet stores
SYNOPSYS
pet-store --pet-store.stargate.public-url=%<publicURL%> --pet-store.stargate.port=%<port%> --pet-store.stargate.operations-secret=%<operationsSecret%> [--pet-store.stargate.concurrent-connections-threshold=%<concurrentConnectionsThreshold%>]
pet-store --pet-store.stargate.public-url=%<publicURL%> --pet-store.stargate.port=%<port%> --pet-store.stargate.operations-secret=%<operationsSecret%> [--pet-store.stargate.log-incoming-http-requests=%<logIncomingHTTPRequests%>] [--pet-store.stargate.log-outgoing-http-requests=%<logOutgoingHTTPRequests%>] [--pet-store.stargate.concurrent-connections-threshold=%<concurrentConnectionsThreshold%>]
PARAMETERS
--pet-store.stargate.public-url=%<publicURL%>
Public URL where the API is deployed. Used to create hypermedia links.
--pet-store.stargate.port=%<port%>
Listening port.
--pet-store.stargate.operations-secret=%<operationsSecret%>
Secret key for checking JWT signatures.
--pet-store.stargate.log-incoming-http-requests=%<logIncomingHTTPRequests%>
Boolean that indicates whether to log all the Incoming HTTP Requests. Defaults to false.
--pet-store.stargate.log-outgoing-http-requests=%<logOutgoingHTTPRequests%>
Boolean that indicates whether to log all the Outgoing HTTP Requests. Defaults to false.
--pet-store.stargate.concurrent-connections-threshold=%<concurrentConnectionsThreshold%>
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
Expand All @@ -337,6 +513,10 @@ ENVIRONMENT
Listening port.
PET_STORE__STARGATE__OPERATIONS_SECRET
Secret key for checking JWT signatures.
PET_STORE__STARGATE__LOG_INCOMING_HTTP_REQUESTS
Boolean that indicates whether to log all the Incoming HTTP Requests. Defaults to false.
PET_STORE__STARGATE__LOG_OUTGOING_HTTP_REQUESTS
Boolean that indicates whether to log all the Outgoing 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)
Expand Down
78 changes: 49 additions & 29 deletions source/Stargate-API-Skeleton/StargateApplication.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ Class {
#name : 'StargateApplication',
#superclass : 'LaunchpadApplication',
#instVars : [
'apiOptional'
'apiOptional',
'znEventToLogRecordAdapter'
],
#classInstVars : [
'Version'
Expand Down Expand Up @@ -87,29 +88,42 @@ 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 Incoming HTTP Requests'
describedBy: 'Boolean that indicates whether to log all the Incoming HTTP Requests'
inside: self sectionsForStargateConfiguration
defaultingTo: false
convertingWith: #asBoolean );
add: ( OptionalConfigurationParameter
named: 'Log Outgoing HTTP Requests'
describedBy: 'Boolean that indicates whether to log all the Outgoing 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' }
Expand Down Expand Up @@ -177,8 +191,9 @@ StargateApplication >> authAlgorithm [
StargateApplication >> basicStartWithin: context [

| api |

self logAPIVersion.
self configureHTTPRequestsLogging.
api := self createAPI.
self
configureGlobalErrorHandlerIn: api;
Expand All @@ -190,6 +205,7 @@ StargateApplication >> basicStartWithin: context [
StargateApplication >> basicStop [

apiOptional withContentDo: [ :api | api stop ].
znEventToLogRecordAdapter stopListeners.
super basicStop
]

Expand Down Expand Up @@ -224,10 +240,13 @@ StargateApplication >> configureGlobalErrorHandlerIn: api [
] unless: self isDebugModeEnabled
]

{ #category : 'private - accessing' }
StargateApplication >> controllersToInstall [
{ #category : 'private - activation/deactivation' }
StargateApplication >> configureHTTPRequestsLogging [

^ self subclassResponsibility
znEventToLogRecordAdapter logOutgoingRequests: self stargateConfiguration logOutgoingHTTPRequests.
znEventToLogRecordAdapter logIncomingRequests: self stargateConfiguration logIncomingHTTPRequests.

znEventToLogRecordAdapter startUpListeners
]

{ #category : 'private - activation/deactivation' }
Expand All @@ -245,7 +264,8 @@ StargateApplication >> createAPI [
StargateApplication >> initialize [

super initialize.
apiOptional := Optional unused
apiOptional := Optional unused.
znEventToLogRecordAdapter := ZnEventToLogRecordAdapter new
]

{ #category : 'private - activation/deactivation' }
Expand Down

0 comments on commit 28a603f

Please sign in to comment.