├── README.md └── versions ├── 0.0.1.md ├── 1.0.0.md ├── 1.1.0.md └── 2.0.0.md /README.md: -------------------------------------------------------------------------------- 1 | # API Log Format (ALF) 2 | 3 | The API Log Format (**ALF**) is a new HTTP logging format that is loosely based on [HTTP Archive Format (HAR)][har-spec] with slight modifications for simplicity. 4 | 5 | ## Versions 6 | 7 | - [v2.0.0](versions/2.0.0.md) - *Proposed* 8 | - [v1.1.0](versions/1.1.0.md) - **Current** 9 | - [v1.0.0](versions/1.0.0.md) - *Deprecated* 10 | - [v0.0.1](versions/0.0.1.md) - *Deprecated* 11 | 12 | ## Tools 13 | 14 | ### Validation 15 | 16 | The official validator is available at [Mashape/alf-validator][alf-validator] 17 | 18 | ### Analytics 19 | 20 | Mashape's [Galileo][galileo] is the Analytics Platform for Monitoring, Visualizing and Inspecting API & Microservice Traffic, utilizes the ALF Format for reporting HTTP traffic using a variety of [agents][agent-spec]. 21 | 22 | [alf-validator]: https://github.com/Mashape/alf-validator "Official Validator" 23 | [agent-spec]: https://github.com/Mashape/galileo-agent-spec 24 | [har-spec]: https://github.com/ahmadnassri/har-spec "Har Specification" 25 | [galileo]: https://getgalileo.io/ "Galileo" 26 | -------------------------------------------------------------------------------- /versions/0.0.1.md: -------------------------------------------------------------------------------- 1 | # API Log Format (ALF) v0.0.1 *(Deprecated)* 2 | 3 | *[skip to the full json example][example]* 4 | 5 | ### Encoding 6 | 7 | A log message is REQUIRED to be saved in `UTF-8` encoding. Other encodings are forbidden. 8 | 9 | ### Differences from HAR 10 | 11 | Due to the nature of HAR originating as a browser based format, some elements are not applicable and will be ignored: 12 | 13 | - `log` 14 | - `browser` 15 | - `pages` 16 | - `entry` 17 | - `pageref` 18 | - `cache` 19 | - `connection` 20 | - `request` 21 | - `cookies` 22 | - `postData` 23 | - `response` 24 | - `cookies` 25 | - `redirectURL` 26 | 27 | *(in addition to the above list all `comment` properties on all objects are ignored.)* 28 | 29 | In addition, the following properties are generated by our official agents, and provide a richer view into HTTP requests in API Analytics: 30 | 31 | + [`serviceToken`][message] 32 | + [`entry.clientIPAddress`][entry] 33 | + [`request.content`][request] 34 | 35 | ## List of objects 36 | 37 | ### message 38 | 39 | This object represents the root of the JSON message. The object contains the following name/value pairs: 40 | 41 | ```json 42 | { 43 | "serviceToken": "", 44 | "har": {}, 45 | } 46 | ``` 47 | 48 | | Name | Type | Required | Description | 49 | | ------------------- | -------- | ---------- | ------------------------------------------------------------------------------------------------------- | 50 | | **`serviceToken`** | `String` | `required` | obtain yours by registering for a free trial at [APIAnalytics.com][analytics-url] | 51 | | **`har`** | [`[HAR]`][har] | `required` | An object containing the version, creator, and log entries. | 52 | 53 | ### har 54 | 55 | A [**HAR**][har-spec] *(HTTP Archive Format)* object. 56 | 57 | ```json 58 | { 59 | "log": {} 60 | } 61 | ``` 62 | 63 | | Name | Type | Required | Description | 64 | | ------------- | -------------- | ---------- | --------------------------------------------------------------- | 65 | | **`log`** | [`[Log]`](#log) | `required` | An object that represents the root of exported HAR data. | 66 | 67 | ### log 68 | 69 | An object that represents the root of exported data. 70 | 71 | ```json 72 | { 73 | "version": "1.2", 74 | "creator": {}, 75 | "entries": [] 76 | } 77 | ``` 78 | 79 | | Name | Type | Required | Description | 80 | | ------------------- | -------- | ---------- | -------------------------------------------------------------------------------------- | 81 | | **`version`** | `String` | `required` | Version number of the format *(currently 1.2)* | 82 | | **`creator`** | [`Creator`][creator] | `required` | An object containing name and version information of the log creator agent | 83 | | **`entries`** | [`[Entry]`][entry] | `required` | An array of objects, each representing one exported (tracked) HTTP request | 84 | 85 | ### creator 86 | 87 | This object contains information about the log creator agent and contains the following name/value pairs: 88 | 89 | ```json 90 | "creator": { 91 | "name": "My HAR client", 92 | "version": "1.0" 93 | } 94 | ``` 95 | 96 | | Name | Type | Required | Description | 97 | | ------------- | -------- | ---------- | ----------------------------------------------------- | 98 | | **`name`** | `String` | `required` | The name of the agent that created the log | 99 | | **`version`** | `String` | `required` | The version number of the agent that created the log | 100 | 101 | ### entry 102 | 103 | This object represents an array with all exported HTTP requests. 104 | 105 | ```json 106 | "entries": [ 107 | { 108 | "startedDateTime": "2015-01-20T18:22:09.052", 109 | "serverIPAddress": "10.10.10.10", 110 | "clientIPAddress": "10.10.10.20", 111 | "time": 50, 112 | "request": {}, 113 | "response": {}, 114 | "timings": {} 115 | } 116 | ] 117 | ``` 118 | 119 | | Name | Type | Required | Description | 120 | | --------------------- | --------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------- | 121 | | **`serverIPAddress`** | `String` | `optional` | IP address of the server | 122 | | **`clientIPAddress`** | `String` | `optional` | IP address of the client | 123 | | **`startedDateTime`** | `String` | `required` | Date and time stamp for the beginning of the request (ISO 8601 - `YYYY-MM-DDThh:mm:ss.sTZD`) | 124 | | **`time`** | `Number` | `optional` | Total elapsed time of the request in milliseconds. This is the sum of all timing metrics available in the [`Timings`][timings] object | 125 | | **`request`** | [`Request`][request] | `required` | An object containing request details. | 126 | | **`response`** | [`Response`][response] | `required` | An object containing response details. | 127 | | **`timings`** | [`Timings`][timings] | `required` | An object containing timing details about request/response round trip | 128 | 129 | ### request 130 | 131 | This object contains detailed info about performed request. 132 | 133 | ```json 134 | "request": { 135 | "method": "GET", 136 | "url": "http://api.domain.com/path/", 137 | "httpVersion": "HTTP/1.1", 138 | "queryString" : [], 139 | "headers": [], 140 | "headersSize" : 50, 141 | "bodySize" : 20, 142 | "content": {} 143 | } 144 | ``` 145 | 146 | | Name | Type | Required | Description | 147 | | ----------------- | --------- | ---------- | ----------------------------------------------------------------------------------------------------------------------- | 148 | | **`method`** | `String` | `required` | Request method | 149 | | **`url`** | `String` | `required` | Absolute URL of the request | 150 | | **`httpVersion`** | `String` | `required` | Request HTTP Version | 151 | | **`queryString`** | [`[QueryString]`][querystring] | `optional` | List of query parameter objects | 152 | | **`headers`** | [`[Headers]`][headers] | `required` | List of header objects | 153 | | **`headersSize`** | `Number` | `required` | Total number of bytes from the start of the HTTP request message until (and including) the double CRLF before the body | 154 | | **`content`** | [`Content`][content] | `optional` | An object containing request body details. | 155 | | **`bodySize`** | `Number` | `optional` | Size of the request body (POST data payload) in bytes. | 156 | 157 | ### response 158 | 159 | This object contains detailed info about the response. 160 | 161 | ```json 162 | "response": { 163 | "status": 200, 164 | "statusText": "OK", 165 | "httpVersion": "HTTP/1.1", 166 | "headers": [], 167 | "headersSize" : 160, 168 | "content": {}, 169 | "bodySize" : 850, 170 | } 171 | ``` 172 | 173 | | Name | Type | Required | Description | 174 | | ----------------- | --------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------- | 175 | | **`status`** | `Number` | `required` | Response status | 176 | | **`statusText`** | `String` | `required` | Response status description | 177 | | **`httpVersion`** | `String` | `required` | Response HTTP Version | 178 | | **`headers`** | [`[Headers]`][headers] | `required` | List of header objects | 179 | | **`headersSize`** | `Number` | `required` | Total number of bytes from the start of the HTTP response message until (and including) the double CRLF before the body | 180 | | **`content`** | [`Content`][content] | `optional` | An object containing details regarding the response body. | 181 | | **`bodySize`** | `Number` | `optional` | Size of the received response body in bytes. Set to zero in case of responses coming from the cache (304) or no body is sent | 182 | 183 | 184 | ### headers 185 | 186 | This object contains list of all headers *(used in [request][request] and [response][response] objects)*. 187 | 188 | ```json 189 | "headers": [ 190 | { 191 | "name": "Accept-Encoding", 192 | "value": "gzip,deflate", 193 | }, 194 | { 195 | "name": "Accept-Language", 196 | "value": "en-us,en;q=0.5", 197 | } 198 | ] 199 | ``` 200 | 201 | ### queryString 202 | 203 | This object contains list of all parameters & values parsed from a query string, if any *(embedded in `request`(#request) object)*. 204 | 205 | ```json 206 | "queryString": [ 207 | { 208 | "name": "param1", 209 | "value": "value1", 210 | }, 211 | { 212 | "name": "param1", 213 | "value": "value1", 214 | } 215 | ] 216 | ``` 217 | 218 | ### content 219 | 220 | This object describes details about response content *(used in [request][request] and [response][response] objects)*. 221 | 222 | ```json 223 | "content": { 224 | "size": 33, 225 | "compression": 0, 226 | "mimeType": "text/html; charset=utf-8", 227 | "encoding": "base64", 228 | "text": "", 229 | } 230 | ``` 231 | 232 | | Name | Type | Required | Description | 233 | | ----------------- | --------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 234 | | **`size`** | `Number` | `required` | Length of the returned content in bytes. *Should be equal to `(request|response).bodySize` if there is no compression and bigger when the content has been compressed* | 235 | | **`compression`** | `Number` | `optional` | Number of bytes saved. Leave out if info is not available | 236 | | **`mimeType`** | `String` | `required` | MIME type of the text field. By default it should be `application/octet-stream`. Include the charset attribute of the MIME type is included, if available. | 237 | | **`encoding`** | `String` | `optional` | Encoding used for the text field e.g `base64`. | 238 | | **`text`** | `String|Null` | `optional` | The body encoded using the format declared in `encoding`. Leave out or `null` if info is not available. Empty string if the information is available, but there is no body or an empty one | 239 | 240 | Note: you should still construct and send `content` object even if not adding the `text` property. 241 | 242 | 243 | ### timings 244 | 245 | This object describes various phases within request-response round trip. All times are specified in milliseconds. 246 | 247 | ```json 248 | "timings": { 249 | "blocked": 0, 250 | "dns": 0, 251 | "connect": 15, 252 | "send": 20, 253 | "wait": 38, 254 | "receive": 12, 255 | "ssl": 0, 256 | } 257 | ``` 258 | 259 | | Name | Type | Required | Description | 260 | | ------------- | -------- | ---------- | ----------------------------------------------------------------- | 261 | | **`blocked`** | `Number` | `optional` | Time spent in a queue waiting for a network connection | 262 | | **`dns`** | `Number` | `optional` | DNS resolution time. The time required to resolve a host name | 263 | | **`connect`** | `Number` | `optional` | Time required to create TCP connection. | 264 | | **`send`** | `Number` | `required` | Time required to send HTTP request to the server | 265 | | **`wait`** | `Number` | `required` | Waiting for a response from the server | 266 | | **`receive`** | `Number` | `required` | Time required to read entire response from the server (or cache) | 267 | | **`ssl`** | `Number` | `optional` | Time required for SSL/TLS negotiation | 268 | 269 | *The `time` value for the [request object][request] must be equal to the sum of the timings supplied in this section.* 270 | 271 | Following must always be true: 272 | 273 | ```js 274 | entry.time = entry.timings.blocked + entry.timings.dns + 275 | entry.timings.connect + entry.timings.send + entry.timings.wait + 276 | entry.timings.receive + entry.timings.ssl; 277 | ``` 278 | 279 | ###### Full Example 280 | 281 | ```json 282 | { 283 | "serviceToken": "", 284 | "har": { 285 | "log": { 286 | "version": "1.2", 287 | "creator": { 288 | "name": "My HAR client", 289 | "version": "1.0" 290 | }, 291 | "entries": [{ 292 | "startedDateTime": "2015-01-20T18:22:09.052", 293 | "serverIPAddress": "10.10.10.10", 294 | "clientIPAddress": "10.10.10.20", 295 | "time": 50, 296 | "request": { 297 | "method": "POST", 298 | "url": "http://api.domain.com/path/", 299 | "httpVersion": "HTTP/1.1", 300 | "queryString": [{ 301 | "name": "foo", 302 | "value": "bar" 303 | }, { 304 | "name": "baz", 305 | "value": "abc" 306 | }], 307 | "headers": [{ 308 | "name": "Accept", 309 | "value": "text/plain" 310 | }, { 311 | "name": "Cookie", 312 | "value": "ijafhIAGWF3Awf93f" 313 | }], 314 | "headersSize": 44, 315 | "bodySize": 14, 316 | "content": { 317 | "size": 14, 318 | "mimeType": "application/json", 319 | "text": "{\"foo\": \"bar\"}" 320 | } 321 | }, 322 | "response": { 323 | "status": 200, 324 | "statusText": "OK", 325 | "httpVersion": "HTTP/1.1", 326 | "headers": [{ 327 | "name": "Content-Length", 328 | "value": "11" 329 | }, { 330 | "name": "Mime-Type", 331 | "value": "text/plain" 332 | }], 333 | "content": { 334 | "size": 11, 335 | "mimeType": "text/plain", 336 | "text": "hello world" 337 | }, 338 | "bodySize": 11, 339 | "headersSize": 41 340 | }, 341 | "timings": { 342 | "blocked": 0, 343 | "dns": 0, 344 | "connect": 15, 345 | "send": 20, 346 | "wait": 38, 347 | "receive": 12, 348 | "ssl": 0 349 | } 350 | }] 351 | } 352 | } 353 | } 354 | ``` 355 | 356 | [analytics-url]: http://apianalytics.com "API Analytics" 357 | [har-spec]: http://www.softwareishard.com/blog/har-12-spec/ "Har Specification" 358 | [example]: #full-example "Example ALF Object" 359 | [message]: #message "Message Object" 360 | [har]: #har "HAR Object" 361 | [creator]: #creator "Creator Object" 362 | [entry]: #entry "Entry Object" 363 | [request]: #request "Request Object" 364 | [response]: #response "Response Object" 365 | [headers]: #headers "Headers Object" 366 | [querystring]: #querystring "QueryString Object" 367 | [content]: #content "Content Object" 368 | [timings]: #timings "Timings Object" 369 | -------------------------------------------------------------------------------- /versions/1.0.0.md: -------------------------------------------------------------------------------- 1 | # API Log Format (ALF) v1.0.0 *(Deprecated)* 2 | 3 | ## Data Structure 4 | 5 | Log messages are **REQUIRED** to be sent in `UTF-8` encoding, other encodings are forbidden. 6 | 7 | Summary of HAR object types: 8 | 9 | - [/](#-root) 10 | - [log](#log) 11 | - [creator](#creator) 12 | - [browser](#browser) 13 | - [pages](#pages) 14 | - [pageTimings](#pagetimings) 15 | - [entries](#entries) 16 | - [request](#request) 17 | - [headers](#headers) 18 | - [cookies](#cookies) 19 | - [queryString](#querystring) 20 | - [postData](#postData) 21 | - [params](#params) 22 | - [response](#response) 23 | - [headers](#headers) 24 | - [cookies](#cookies) 25 | - [content](#content) 26 | - [cache](#cache) 27 | - [timings](#timings) 28 | 29 | --- 30 | 31 | ### `/` *(root)* 32 | 33 | This object represents the root of exported data. 34 | 35 | ```json 36 | { 37 | "version" : "2.0.0", 38 | "serviceToken": "", 39 | "environment": "", 40 | "clientIPAddress": "", 41 | "har": {}, 42 | } 43 | ``` 44 | 45 | | name | type | required | default | description | 46 | | ------------------- | --------- | --------- | ------- | ----------------------------------------------------------------------------- | 47 | | **version** | `String` | ✔️ | `2.0.0` | Version number of the format | 48 | | **serviceToken** | `String` | ✔️ | `N/A` | Service Token *(e.g. [Galileo Analytics][galileo])* | 49 | | **environment** | `String` | ✖️ | `N/A` | server environment name | 50 | | **clientIPAddress** | `String` | ✖️ | `N/A` | IP address of the client originating the request | 51 | | **har** | `Object` | ✔️ | `N/A` | please refer to the [Official **HAR** *(HTTP Archive Format)* Spec][har-spec] | 52 | 53 | --- 54 | 55 | ### log 56 | 57 | This object represents the root of exported data. 58 | 59 | ```json 60 | "log": { 61 | "version" : "1.2", 62 | "creator" : {}, 63 | "browser" : {}, 64 | "pages": [], 65 | "entries": [], 66 | "comment": "" 67 | } 68 | ``` 69 | 70 | | name | type | required | default | description | 71 | | ------------------------- | --------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------- | 72 | | **version** | `string` | ✔️ | `1.3` | Version number of the format | 73 | | [**creator**](#creator) | `object` | ✔️ | `N/A` | Name and version info of the log [creator](#creator) application | 74 | | [**browser**](#browser) | `object` | ✖️ | `N/A` | Name and version info of used [browser](#browser) | 75 | | [**pages**](#pages) | `array` | ✖️ | `N/A` | List of all exported (tracked) [pages](#pages) | 76 | | [**entries**](#entries) | `array` | ✔️ | `N/A` | List of all exported (tracked) [requests](#entries) | 77 | | **comment** | `string` | ✖️ | `N/A` | A comment provided by the user or the application | 78 | 79 | > There is one [`page`](#pages) object for every exported web page and one [`entry`](#entries) object for every HTTP request. In case when an HTTP trace tool isn't able to group requests by a page, the `pages` array is empty and individual requests doesn't have a parent page. 80 | 81 | 82 | --- 83 | 84 | ### creator 85 | 86 | ```json 87 | "creator": { 88 | "name": "Firebug", 89 | "version": "1.6", 90 | "comment": "" 91 | } 92 | ``` 93 | 94 | | name | type | required | default | description | 95 | | ------------- | --------- | --------- | ------- | ------------------------------------------------------------------ | 96 | | **name** | `string` | ✔️ | `N/A` | Name of the application used to export the log | 97 | | **version** | `string` | ✔️ | `N/A` | Version of the application used to export the log | 98 | | **comment** | `string` | ✖️ | `N/A` | A comment provided by the user or the application | 99 | 100 | 101 | --- 102 | 103 | ### browser 104 | 105 | ```json 106 | "browser": { 107 | "name": "Firefox", 108 | "version": "3.6", 109 | "comment": "" 110 | } 111 | ``` 112 | 113 | | name | type | required | default | description | 114 | | ------------- | --------- | --------- | ------- | ------------------------------------------------------------------ | 115 | | **name** | `string` | ✔️ | `N/A` | Name of the browser used to export the log | 116 | | **version** | `string` | ✔️ | `N/A` | Version of the browser used to export the log | 117 | | **comment** | `string` | ✖️ | `N/A` | A comment provided by the user or the application | 118 | 119 | 120 | --- 121 | 122 | ### pages 123 | 124 | This object represents list of exported pages. 125 | 126 | ```json 127 | "pages": [ 128 | { 129 | "startedDateTime": "2009-04-16T12:07:25.123+01:00", 130 | "id": "page_0", 131 | "title": "Test Page", 132 | "pageTimings": {}, 133 | "comment": "" 134 | } 135 | ] 136 | ``` 137 | 138 | | name | type | required | default | description | 139 | | --------------------------------- | --------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------- | 140 | | **startedDateTime** | `string` | ✔️ | `N/A` | Date and time stamp for the beginning of the page load ([`ISO 8601`][iso-8601]) | 141 | | **id** | `string` | ✔️ | `N/A` | Unique identifier of a page within the [`log`](#log). [`Entries`](#entries) use it to refer the parent page | 142 | | **title** | `string` | ✔️ | `N/A` | Page title | 143 | | [**pageTimings**](#pagetimings) | `object` | ✔️ | `N/A` | Detailed timing info about page load | 144 | | **comment** | `string` | ✖️ | `N/A` | A comment provided by the user or the application | 145 | 146 | 147 | --- 148 | 149 | ### pageTimings 150 | 151 | This object describes timings for various events (states) fired during the page load. All times are specified in milliseconds. If a time info is not available appropriate field is set to `-1` 152 | 153 | ```json 154 | "pageTimings": { 155 | "onContentLoad": 1720, 156 | "onLoad": 2500, 157 | "comment": "" 158 | } 159 | ``` 160 | 161 | | name | type | required | default | description | 162 | | ----------------- | --------- | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------- | 163 | | **onContentLoad** | `number` | ✖️ | `-1` | Content of the page loaded. Number of milliseconds since page load started ([`page.startedDateTime`](#pages)) | 164 | | **onLoad** | `number` | ✖️ | `-1` | Page is loaded (`onLoad` event fired). Number of milliseconds since page load started ([`page.startedDateTime`](#pages)) | 165 | | **comment** | `string` | ✖️ | `N/A` | A comment provided by the user or the application | 166 | 167 | > Depeding on the browser, `onContentLoad` property represents `DOMContentLoad` event or `document.readyState == interactive`. 168 | 169 | 170 | --- 171 | 172 | ### entries 173 | 174 | This object represents an array with all exported HTTP requests. Sorting entries by `startedDateTime` (starting from the oldest) is preferred way how to export data since it can make importing faster. However the reader application should always make sure the array is sorted (if required for the import). 175 | 176 | ```json 177 | "entries": [ 178 | { 179 | "pageref": "page_0", 180 | "startedDateTime": "2009-04-16T12:07:23.596Z", 181 | "time": 50, 182 | "request": {}, 183 | "response": {}, 184 | "cache": {}, 185 | "timings": {}, 186 | "serverIPAddress": "10.0.0.1", 187 | "connection": "52492", 188 | "comment": "" 189 | } 190 | ] 191 | ``` 192 | 193 | | name | type | required | default | description | 194 | | ------------------------- | --------- | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | 195 | | **pageref** | `string` | ✖️ | `N/A` | Unique Reference to the parent page | 196 | | **startedDateTime** | `string` | ✔️ | `N/A` | Date and time stamp of the request start ([`ISO 8601`][iso-8601]) | 197 | | **time** | `number` | ✔️ | `0` | Total elapsed time of the request in milliseconds. This is the sum of all timings available in the timings object (i.e. not including `-1` values) | 198 | | [**request**](#request) | `object` | ✔️ | `N/A` | Detailed info about the request | 199 | | [**response**](#response) | `object` | ✔️ | `N/A` | Detailed info about the response | 200 | | [**cache**](#cache) | `object` | ✔️ | `N/A` | Info about cache usage | 201 | | [**timings**](#timings) | `object` | ✔️ | `N/A` | Detailed timing info about request/response round trip | 202 | | **serverIPAddress** | `string` | ✖️ | `N/A` | IP address of the server that was connected (result of DNS resolution) | 203 | | **connection** | `string` | ✖️ | `N/A` | Unique ID of the parent TCP/IP connection, can be the client or server port number. | 204 | | **comment** | `string` | ✖️ | `N/A` | A comment provided by the user or the application | 205 | 206 | > `connection`: Note that a port number doesn't have to be unique identifier in cases where the port is shared for more connections. If the port isn't available for the application, any other unique connection ID can be used instead (e.g. connection index) 207 | 208 | 209 | --- 210 | 211 | ### request 212 | 213 | This object contains detailed info about performed request. 214 | 215 | ```json 216 | "request": { 217 | "method": "GET", 218 | "url": "http://www.example.com/path/?param=value", 219 | "httpVersion": "HTTP/1.1", 220 | "cookies": [], 221 | "headers": [], 222 | "queryString" : [], 223 | "postData" : {}, 224 | "headersSize" : 150, 225 | "bodySize" : 0, 226 | "comment" : "" 227 | } 228 | ``` 229 | 230 | | name | type | required | default | description | 231 | | --------------------------------- | --------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------ | 232 | | **method** | `string` | ✔️ | `N/A` | [Request method][rfc7231-methods] | 233 | | **url** | `string` | ✔️ | `N/A` | Absolute URL of the request (fragments are not included) | 234 | | **httpVersion** | `string` | ✔️ | `N/A` | [Request HTTP Version][rfc7230-version] | 235 | | [**cookies**](#cookies) | `array` | ✔️ | `N/A` | List of [`cookie`](#cookies) objects | 236 | | [**headers**](#headers) | `array` | ✔️ | `N/A` | List of [`header`](#headers) objects | 237 | | [**queryString**](#querystring) | `array` | ✔️ | `N/A` | List of query parameter objects | 238 | | [**postData**](#postdata) | `object` | ✖️ | `N/A` | Posted data info | 239 | | **headersSize** | `number` | ✔️ | `-1` | Total number of bytes from the start of the HTTP request message until (and including) the double `CRLF` before the body | 240 | | **bodySize** | `number` | ✔️ | `-1` | Size of the request body in bytes (e.g. `POST` data payload) | 241 | | **comment** | `string` | ✖️ | `N/A` | A comment provided by the user or the application | 242 | 243 | 244 | > The total request size sent can be computed as follows *(if both values are available)*: 245 | > 246 | > ```js 247 | > let totalSize = entry.request.headersSize + entry.request.bodySize 248 | > ``` 249 | 250 | --- 251 | 252 | ### response 253 | 254 | This object contains detailed info about the response. 255 | 256 | ```json 257 | "response": { 258 | "status": 200, 259 | "statusText": "OK", 260 | "httpVersion": "HTTP/1.1", 261 | "cookies": [], 262 | "headers": [], 263 | "content": {}, 264 | "redirectURL": "", 265 | "headersSize" : 160, 266 | "bodySize" : 850, 267 | "comment" : "" 268 | } 269 | ``` 270 | 271 | | name | type | required | default | description | 272 | | ------------------------- | --------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------- | 273 | | **status** | `number` | ✔️ | `N/A` | [Response status][rfc7231-status] | 274 | | **statusText** | `string` | ✔️ | `N/A` | [Response status description][rfc7231-status] | 275 | | **httpVersion** | `string` | ✔️ | `N/A` | [Response HTTP Version][rfc7230-version] | 276 | | [**cookies**](#cookies) | `array` | ✔️ | `N/A` | List of [`cookie`](#cookies) objects | 277 | | [**headers**](#headers) | `array` | ✔️ | `N/A` | List of [`header`](#headers) objects | 278 | | [**content**](#content) | `object` | ✔️ | `N/A` | Details about the response body | 279 | | **redirectURL** | `string` | ✔️ | `N/A` | Redirection target URL from the Location response header | 280 | | **headersSize** | `number` | ✔️ | `-1` | Total number of bytes from the start of the HTTP response message until (and including) the double `CRLF` before the body | 281 | | **bodySize** | `number` | ✔️ | `-1` | Size of the received response body in bytes. Set to `0` in case of responses coming from the cache (`304`) | 282 | | **comment** | `string` | ✖️ | `N/A` | A comment provided by the user or the application | 283 | 284 | > - `headersSize`: The size of received response-headers is computed only from headers that are really received from the server. Additional headers appended by the browser are not included in this number, but they appear in the list of header objects. 285 | > - The total response size received can be computed as follows *(if both values are available)*: 286 | > ```js 287 | > let totalSize = entry.response.headersSize + entry.response.bodySize 288 | > ``` 289 | 290 | 291 | --- 292 | 293 | ### cookies 294 | 295 | This object contains list of all cookies (used in [`request`](#request) and [`response`](#response) objects). 296 | 297 | ```json 298 | "cookies": [ 299 | { 300 | "name": "TestCookie", 301 | "value": "Cookie Value", 302 | "path": "/", 303 | "domain": "www.janodvarko.cz", 304 | "expires": "2009-07-24T19:20:30.123+02:00", 305 | "httpOnly": false, 306 | "secure": false, 307 | "comment": "" 308 | } 309 | ] 310 | ``` 311 | 312 | | name | type | required | default | description | 313 | | ------------- | --------- | --------- | ------- | ----------------------------------------------------------------------------------- | 314 | | **name** | `string` | ✔️ | `N/A` | The name of the cookie | 315 | | **value** | `string` | ✔️ | `N/A` | The cookie value | 316 | | **path** | `string` | ✖️ | `N/A` | The path pertaining to the cookie | 317 | | **domain** | `string` | ✖️ | `N/A` | The host of the cookie | 318 | | **expires** | `string` | ✖️ | `N/A` | Cookie expiration time. ([`ISO 8601`][iso-8601]) | 319 | | **httpOnly** | `boolean` | ✖️ | `N/A` | Set to true if the cookie is HTTP only, false otherwise | 320 | | **secure** | `boolean` | ✖️ | `N/A` | true` if the cookie was transmitted over ssl, `false` otherwise | 321 | | **comment** | `string` | ✖️ | `N/A` | A comment provided by the user or the application | 322 | 323 | 324 | --- 325 | 326 | ### headers 327 | 328 | This object contains list of all headers (used in [`request`](#request) and [`response`](#response) objects). 329 | 330 | ```json 331 | "headers": [ 332 | { 333 | "name": "Accept-Encoding", 334 | "value": "gzip,deflate", 335 | "comment": "" 336 | }, 337 | { 338 | "name": "Accept-Language", 339 | "value": "en-us,en;q=0.5", 340 | "comment": "" 341 | } 342 | ] 343 | ``` 344 | 345 | | name | type | required | default | description | 346 | | ------------- | --------- | --------- | ------- | ------------------------------------------------------------------ | 347 | | **name** | `string` | ✔️ | `N/A` | The name of the header | 348 | | **value** | `string` | ✔️ | `N/A` | The header value | 349 | | **comment** | `string` | ✖️ | `N/A` | A comment provided by the user or the application | 350 | 351 | 352 | --- 353 | 354 | ### queryString 355 | 356 | This object contains list of all parameters & values parsed from a query string, if any (embedded in [`request`](#request) object). 357 | 358 | ```json 359 | "queryString": [ 360 | { 361 | "name": "param1", 362 | "value": "value1", 363 | "comment": "" 364 | }, 365 | { 366 | "name": "param1", 367 | "value": "value1", 368 | "comment": "" 369 | } 370 | ] 371 | ``` 372 | 373 | | name | type | required | default | description | 374 | | ------------- | --------- | --------- | ------- | ------------------------------------------------------------------ | 375 | | **name** | `string` | ✔️ | `N/A` | The name of the query | 376 | | **value** | `string` | ✔️ | `N/A` | The query value | 377 | | **comment** | `string` | ✖️ | `N/A` | A comment provided by the user or the application | 378 | 379 | 380 | --- 381 | 382 | ### postData 383 | 384 | This object describes posted data, if any (embedded in [`request`](#request) object). 385 | 386 | ```json 387 | "postData": { 388 | "mimeType": "multipart/form-data", 389 | "params": [], 390 | "text" : "plain posted data", 391 | "comment": "" 392 | } 393 | ``` 394 | 395 | | name | type | required | default | description | 396 | | --------------------- | --------- | --------- | ------- | -------------------------------------------------------------------- | 397 | | **mimeType** | `string` | ✔️ | `N/A` | [Mime type][mime-type] of posted data | 398 | | [**params**](#params) | `array` | ✔️ * | `N/A` | List of posted parameters (in case of URL encoded parameters) | 399 | | **text** | `string` | ✔️ * | `N/A` | Plain text posted data | 400 | | **comment** | `string` | ✖️ | `N/A` | A comment provided by the user or the application | 401 | 402 | > - `text` and `params` fields are mutually exclusive. 403 | 404 | --- 405 | 406 | ### params 407 | 408 | List of posted parameters, if any (embedded in [`postData`](#postData) object). 409 | 410 | ```json 411 | "params": [ 412 | { 413 | "name": "paramName", 414 | "value": "paramValue", 415 | "fileName": "example.pdf", 416 | "contentType": "application/pdf", 417 | "comment": "" 418 | } 419 | ] 420 | ``` 421 | 422 | | name | type | required | default | description | 423 | | ----------------- | --------- | --------- | ------- | ------------------------------------------------------------------- | 424 | | **name** | `string` | ✔️ | `N/A` | name of a posted parameter | 425 | | **value** | `string` | ✖️ | `N/A` | value of a posted parameter or content of a posted file | 426 | | **fileName** | `string` | ✖️ | `N/A` | name of a posted file | 427 | | **contentType** | `string` | ✖️ | `N/A` | content type of a posted file | 428 | | **comment** | `string` | ✖️ | `N/A` | A comment provided by the user or the application | 429 | 430 | ###### Example 431 | 432 | > **encoded** 433 | > ```json 434 | > "postData": { 435 | > "mimeType": "multipart/form-data", 436 | > "comment": "", 437 | > "params": [ 438 | > { 439 | > "name": "image", 440 | > "value": "W2JpbmFyeSBkYXRhXQ==", 441 | > "encoding": "base64", 442 | > "fileName": "example.jpg", 443 | > "contentType": "image/jpeg", 444 | > "comment": "" 445 | > } 446 | > ] 447 | > } 448 | > ``` 449 | 450 | 451 | --- 452 | 453 | ### content 454 | 455 | This object describes details about response content (embedded in [`response`](#response) object). 456 | 457 | ```json 458 | "content": { 459 | "size": 33, 460 | "compression": 0, 461 | "mimeType": "text/html; charset=utf-8", 462 | "text": "\n", 463 | "comment": "" 464 | } 465 | ``` 466 | 467 | | name | type | required | default | description | 468 | | ----------------- | --------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | 469 | | **size** | `number` | ✔️ | `N/A` | Length of the returned content in bytes. Should be equal to `response.bodySize` if there is no compression and bigger when the content has been compressed | 470 | | **compression** | `number` | ✖️ | `N/A` | Number of bytes saved | 471 | | **mimeType** | `string` | ✔️ | `N/A` | MIME type of the response `text` *(value of the `Content-Type` response header)*. The charset attribute of the MIME type is included *(if available)* | 472 | | **text** | `string` | ✖️ | `N/A` | Response body sent from the server or loaded from the browser cache. | 473 | | **encoding** | `string` | ✖️ | `N/A` | Encoding used for response `text` field e.g "base64" | 474 | | **comment** | `string` | ✖️ | `N/A` | A comment provided by the user or the application | 475 | 476 | > - The `text` field is populated with textual content only 477 | > - The `text` field is either HTTP decoded text or a encoded (e.g. "base64") representation of the response body 478 | > - Leave out the `encoding` field if the text field is HTTP decoded (decompressed & unchunked), than trans-coded from its original character set into `UTF-8` 479 | > - Before setting the text field, the HTTP response is decoded (decompressed & unchunked), than trans-coded from its original character set into `UTF-8`. 480 | > - Additionally, it can be encoded using e.g. base64. 481 | > - Ideally, the application should be able to unencode a base64 blob and get a byte-for-byte identical resource to what the browser operated on. 482 | > - `encoding` field is useful for including binary responses (e.g. images) into the HAR file. 483 | 484 | ###### Example 485 | 486 | > **original** 487 | 488 | > ```html 489 | > \n 490 | > ``` 491 | 492 | > **encoded** 493 | > ```json 494 | > "content": { 495 | > "size": 33, 496 | > "compression": 0, 497 | > "mimeType": "text/html; charset=utf-8", 498 | > "text": "PGh0bWw+PGhlYWQ+PC9oZWFkPjxib2R5Lz48L2h0bWw+XG4=", 499 | > "encoding": "base64", 500 | > "comment": "" 501 | > } 502 | > ``` 503 | 504 | 505 | --- 506 | 507 | ### cache 508 | 509 | This objects contains info about a request coming from browser cache. 510 | 511 | ```json 512 | "cache": { 513 | "beforeRequest": {}, 514 | "afterRequest": {}, 515 | "comment": "" 516 | } 517 | ``` 518 | 519 | | name | type | required | default | description | 520 | | ------------------------------------------------- | --------- | --------- | ------- | ------------------------------------------------------------------- | 521 | | [**beforeRequest**](#beforerequest--afterrequest) | `object` | ✖️ | `N/A` | State of a cache entry before the request | 522 | | [**afterRequest**](#beforerequest--afterrequest) | `object` | ✖️ | `N/A` | State of a cache entry after the request | 523 | | **comment** | `string` | ✖️ | `N/A` | A comment provided by the user or the application | 524 | 525 | ###### Example 1 526 | 527 | No cache information are available (or you can just leave out the entire field). 528 | 529 | > ```json 530 | > "cache": {} 531 | > ``` 532 | 533 | ###### Example 2 534 | 535 | Info about the cache entry before request is not available and there is no cache entry after the request. 536 | 537 | > ```json 538 | > "cache": { 539 | > "afterRequest": null 540 | > } 541 | > ``` 542 | 543 | ###### Example 3 544 | 545 | No cache entry before nor after the request. 546 | 547 | > ```json 548 | > "cache": { 549 | > "beforeRequest": null, 550 | > "afterRequest": null 551 | > } 552 | > ``` 553 | 554 | ###### Example 4 555 | 556 | Indicate that the entry was not in the cache but was store after the content was downloaded by the request. 557 | 558 | > ```json 559 | > "cache": { 560 | > "beforeRequest": null, 561 | > "afterRequest": { 562 | > "expires": "2009-04-16T15:50:36", 563 | > "lastAccess": "2009-16-02T15:50:34", 564 | > "eTag": "", 565 | > "hitCount": 0, 566 | > "comment": "" 567 | > } 568 | > } 569 | > ``` 570 | 571 | --- 572 | 573 | ### beforeRequest / afterRequest 574 | 575 | ```json 576 | "beforeRequest": { 577 | "expires": "2009-04-16T15:50:36", 578 | "lastAccess": "2009-16-02T15:50:34", 579 | "eTag": "", 580 | "hitCount": 0, 581 | "comment": "" 582 | } 583 | ``` 584 | 585 | | name | type | required | default | description | 586 | | ----------------- | --------- | --------- | ------- | ------------------------------------------------------------------- | 587 | | **expires** | `string` | ✖️ | `N/A` | Expiration time of the cache entry | 588 | | **lastAccess** | `string` | ✔️ | `N/A` | The last time the cache entry was opened | 589 | | **eTag** | `string` | ✔️ | `N/A` | [Etag][rfc7232-etag] | 590 | | **hitCount** | `number` | ✔️ | `N/A` | The number of times the cache entry has been opened | 591 | | **comment** | `string` | ✖️ | `N/A` | A comment provided by the user or the application | 592 | 593 | 594 | --- 595 | 596 | ### timings 597 | 598 | This object describes various phases within request-response round trip. All times are specified in milliseconds. 599 | 600 | ```json 601 | "timings": { 602 | "blocked": 0, 603 | "dns": -1, 604 | "connect": 15, 605 | "send": 20, 606 | "wait": 38, 607 | "receive": 12, 608 | "ssl": -1, 609 | "comment": "" 610 | } 611 | ``` 612 | 613 | | name | type | required | default | description | 614 | | ------------- | --------- | --------- | ------- | ------------------------------------------------------------------- | 615 | | **blocked** | `number` | ✖️ | `-1` | Time spent in a queue waiting for a network connection | 616 | | **dns** | `number` | ✖️ | `-1` | DNS resolution time. The time required to resolve a host name | 617 | | **connect** | `number` | ✖️ | `-1` | Time required to create TCP connection | 618 | | **send** | `number` | ✔️ | `N/A` | Time required to send HTTP request to the server | 619 | | **wait** | `number` | ✔️ | `N/A` | Waiting for a response from the server | 620 | | **receive** | `number` | ✔️ | `N/A` | Time required to read entire response from the server (or cache) | 621 | | **ssl** | `number` | ✖️ | `-1` | Time required for SSL/TLS negotiation. | 622 | | **comment** | `string` | ✖️ | `N/A` | A comment provided by the user or the application | 623 | 624 | > - If the `ssl` field is defined then the time is also included in the connect field *(to ensure backward compatibility with HAR `v1.1`)* 625 | > - The `send`, `wait` and `receive` timings are not optional and **must** have non-negative values. 626 | > - An exporting tool can omit the blocked, dns, connect and ssl, timings on every request if it is unable to provide them. 627 | > - Tools that can provide these timings can set their values to `-1` if they don’t apply. For example, connect would be `-1` for requests which re-use an existing connection. 628 | > - The `time` value for the request must be equal to the sum of the timings supplied in this section *(excluding any `-1` values)*. 629 | > - The Following must be true in case there are no `-1` values (`entry` is an object in [`log.entries`](#entries)) : 630 | > ```js 631 | > entry.time == entry.timings.blocked + entry.timings.dns + entry.timings.connect 632 | > entry.timings.send + entry.timings.wait + entry.timings.receive 633 | > ``` 634 | 635 | 636 | --- 637 | 638 | ###### Full Example 639 | 640 | ```json 641 | { 642 | "version": "1.0.0", 643 | "serviceToken": "", 644 | "environment": "PRODUCTION", 645 | "clientIPAddress": "10.10.10.20", 646 | "har": { 647 | "log": { 648 | "version": "1.2", 649 | "creator": { 650 | "name": "HAR Logger", 651 | "version": "1.0.0" 652 | }, 653 | "entries": [ 654 | { 655 | "startedDateTime": "2016-03-13T03:47:16.937Z", 656 | "serverIPAddress": "10.10.10.10", 657 | "time": 82, 658 | "request": { 659 | "method": "POST", 660 | "url": "https://mockbin.org/request", 661 | "httpVersion": "HTTP/1.1", 662 | "queryString": [ 663 | { "name": "foo", "value": "bar" }, 664 | { "name": "baz", "value": "abc" } 665 | ], 666 | "headers": [ 667 | { "name": "accept", "value": "*/*" }, 668 | { "name": "content-length", "value": "25" }, 669 | { "name": "content-type", "value": "application/x-www-form-urlencoded" } 670 | ], 671 | "cookies": [ 672 | { 673 | "name": "foo", 674 | "expires": "2015-02-10T07:33:17.146Z", 675 | "value": "bar", 676 | "httpOnly": false, 677 | "secure": false 678 | } 679 | ], 680 | "headersSize": 62, 681 | "bodySize": 25, 682 | "postData": { 683 | "mimeType": "application/x-www-form-urlencoded", 684 | "text": "foo=bar&bar=baz", 685 | "params": [ 686 | { "name": "foo", "value": "bar" }, 687 | { "name": "bar", "value": "baz" } 688 | ] 689 | } 690 | }, 691 | "response": { 692 | "status": 200, 693 | "statusText": "OK", 694 | "httpVersion": "HTTP/1.1", 695 | "headers": [ 696 | { "name": "content-length", "value": "25" }, 697 | { "name": "content-type", "value": "application/json; charset=utf-8" }, 698 | { "name": "x-powered-by", "value": "mockbin" } 699 | ], 700 | "cookies": [{ 701 | "name": "TestCookie", 702 | "value": "Cookie Value", 703 | "path": "/", 704 | "domain": "www.janodvarko.cz", 705 | "expires": "2009-07-24T19:20:30.123+02:00", 706 | "httpOnly": false, 707 | "secure": false, 708 | }], 709 | "content": { 710 | "size": 23, 711 | "mimeType": "application/json", 712 | "text": "{\"foo\": \"bar\"}", 713 | "compression": -17 714 | }, 715 | "redirectURL": "", 716 | "headersSize": 87, 717 | "bodySize": 25 718 | }, 719 | "cache": { 720 | "beforeRequest": null, 721 | "afterRequest": { 722 | "expires": "2009-04-16T15:50:36", 723 | "lastAccess": "2009-16-02T15:50:34", 724 | "eTag": "", 725 | "hitCount": 0, 726 | } 727 | }, 728 | "timings": { 729 | "blocked": -1, 730 | "dns": -1, 731 | "connect": -1, 732 | "send": 0.06, 733 | "wait": 87.26, 734 | "receive": 0.24, 735 | "ssl": -1 736 | }, 737 | "connection": "3178207" 738 | } 739 | ] 740 | } 741 | } 742 | } 743 | ``` 744 | 745 | [galileo]: https://getgalileo.io/ "Galileo" 746 | [har-spec]: https://github.com/ahmadnassri/har-spec/blob/master/versions/1.2.md "Har 1.2 Specification" 747 | [iso-8601]: http://en.wikipedia.org/wiki/ISO_8601 "ISO 8601" 748 | [mime-type]: http://httpwg.github.io/specs/rfc7231.html#media.type 749 | [rfc7230-uri]: http://httpwg.org/specs/rfc7230.html#uri "RFC7230" 750 | [rfc7230-version]: http://httpwg.github.io/specs/rfc7230.html#http.version "RFC7230" 751 | [rfc7231-methods]: http://httpwg.github.io/specs/rfc7231.html#methods "RFC7231" 752 | [rfc7231-status]: http://httpwg.github.io/specs/rfc7231.html#status.codes "RFC7231" 753 | [rfc7232-etag]: http://httpwg.org/specs/rfc7232.html#header.etag 754 | [rfc7540-headers]: http://httpwg.org/specs/rfc7540.html#HeaderBlock 755 | [semver]: http://semver.org/ "Semantic Versioning" 756 | -------------------------------------------------------------------------------- /versions/1.1.0.md: -------------------------------------------------------------------------------- 1 | # API Log Format (ALF) v1.1.0 *(Current)* 2 | 3 | ## Data Structure 4 | 5 | Log messages are **REQUIRED** to be sent in `UTF-8` encoding, other encodings are forbidden. 6 | 7 | Summary of HAR object types: 8 | 9 | - [/](#-root) 10 | - har 11 | - [log](#log) 12 | - [creator](#creator) 13 | - [entries](#entries) 14 | - [timings](#timings) 15 | - [request](#request) 16 | - [headers](#headers) 17 | - [queryString](#querystring) 18 | - [postData](#postdata) 19 | - [response](#response) 20 | - [headers](#headers) 21 | - [content](#content) 22 | 23 | --- 24 | 25 | ### `/` *(root)* 26 | 27 | This object represents the root of exported data. 28 | 29 | ```json 30 | { 31 | "version" : "1.1.0", 32 | "serviceToken": "", 33 | "environment": "", 34 | "har": {} 35 | } 36 | ``` 37 | 38 | | name | type | required | default | description | 39 | | ---------------- | -------- | -------- | ------- | ----------------------------------------------------------------------------- | 40 | | **version** | `String` | `✔`️ | `1.1.0` | Version number of the format | 41 | | **serviceToken** | `String` | `✔`️ | `N/A` | Service Token *(e.g. [Galileo Analytics][galileo])* | 42 | | **environment** | `String` | `✖`️ | `N/A` | server environment name | 43 | | **har** | `Object` | `✔`️ | `N/A` | please refer to the [Official **HAR** *(HTTP Archive Format)* Spec][har-spec] | 44 | 45 | --- 46 | 47 | ### log 48 | 49 | This object represents the root of exported data. 50 | 51 | ```json 52 | "log": { 53 | "creator" : {}, 54 | "entries": [] 55 | } 56 | ``` 57 | 58 | | name | type | required | default | description | 59 | | ----------------------- | -------- | -------- | ------- | ---------------------------------------------------------------- | 60 | | [**creator**](#creator) | `Object` | `✔`️ | `N/A` | Name and version info of the log [creator](#creator) application | 61 | | [**entries**](#entries) | `Array` | `✔`️ | `N/A` | List of all exported (tracked) [requests](#entries) | 62 | 63 | 64 | --- 65 | 66 | ### creator 67 | 68 | ```json 69 | "creator": { 70 | "name": "galileo-agent-node", 71 | "version": "1.0.0" 72 | } 73 | ``` 74 | 75 | | name | type | required | default | description | 76 | | ----------- | -------- | -------- | ------- | ------------------------------------------------- | 77 | | **name** | `String` | `✔`️ | `N/A` | Name of the application used to export the log | 78 | | **version** | `String` | `✔`️ | `N/A` | Version of the application used to export the log | 79 | 80 | 81 | --- 82 | 83 | ### entries 84 | 85 | This object represents an array with all exported HTTP requests. 86 | 87 | ```json 88 | "entries": [ 89 | { 90 | "startedDateTime": "2016-03-13T03:47:16.937Z", 91 | "serverIPAddress": "10.10.10.10", 92 | "clientIPAddress": "10.10.10.20", 93 | "time": 82, 94 | "request": {}, 95 | "response": {}, 96 | "timings": {} 97 | } 98 | ] 99 | ``` 100 | 101 | | name | type | required | default | description | 102 | | ------------------------- | -------- | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | 103 | | **time** | `Number` | `✔`️ | `0` | Total elapsed time of the request in milliseconds. This is the sum of all timings available in the timings object (i.e. not including `-1` values) | 104 | | **startedDateTime** | `String` | `✔`️ | `N/A` | Date and time stamp of the request start ([`ISO 8601`][iso-8601]) | 105 | | **clientIPAddress** | `String` | `✖`️ | `N/A` | IP address of the client originating the request | 106 | | **serverIPAddress** | `String` | `✖`️ | `N/A` | IP address of the server that was connected (result of DNS resolution) | 107 | | [**request**](#request) | `Object` | `✔`️ | `N/A` | Detailed info about the request | 108 | | [**response**](#response) | `Object` | `✔`️ | `N/A` | Detailed info about the response | 109 | | [**timings**](#timings) | `Object` | `✔`️ | `N/A` | Detailed timing info about request/response round trip | 110 | 111 | 112 | --- 113 | 114 | ### request 115 | 116 | This object contains detailed info about performed request. 117 | 118 | ```json 119 | "request": { 120 | "httpVersion": "HTTP/1.1", 121 | "method": "POST", 122 | "url": "https://mockbin.org/request", 123 | "headers": [], 124 | "queryString" : [], 125 | "postData" : {}, 126 | "headersSize": 62, 127 | "bodyCaptured": true, 128 | "bodySize": 25 129 | } 130 | ``` 131 | 132 | | name | type | required | default | description | 133 | | ------------------------------- | --------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | 134 | | **method** | `String` | `✔`️ | `N/A` | [Request method][rfc7231-methods] | 135 | | **url** | `String` | `✔`️ | `N/A` | Absolute URL of the request (fragments are not included) | 136 | | **httpVersion** | `String` | `✔`️ | `N/A` | [Request HTTP Version][rfc7230-version] | 137 | | **headersSize** | `Number` | `✔`️ | `0` | Total number of bytes from the start of the HTTP request message until (and including) the double `CRLF` before the body | 138 | | **bodyCaptured** | `Boolean` | `✔`️ | `false` | Indicates whether request body was available and could be transmitted as `postData.text`, regardless of whether the body is actually transmitted in the ALF. | 139 | | **bodySize** | `Number` | `✔`️ | `0` | Size of the request body in bytes (e.g. `POST` payload) | 140 | | [**headers**](#headers) | `Array` | `✔`️ | `N/A` | List of [`header`](#headers) objects | 141 | | [**queryString**](#querystring) | `Array` | `✔`️ | `N/A` | List of query parameter objects | 142 | | [**postData**](#postdata) | `Object` | `✖`️ | `N/A` | Posted data info | 143 | 144 | #### Notes 145 | 146 | ##### bodyCaptured 147 | 148 | This value is to determine whether at the time of the `request` the **mechanism** (Manual request, Agent request, etc) sending information had the **body data** available and could be sent in the case that the mechanism chose not to. 149 | 150 | ##### Calculating Total Request Size 151 | 152 | The total request size sent can be computed as follows *(if both values are available)*: 153 | 154 | ```js 155 | // JavaScript Psuedocode 156 | let totalRequestSize = entry.request.headersSize + entry.request.bodySize 157 | ``` 158 | 159 | --- 160 | 161 | ### response 162 | 163 | This object contains detailed info about the response. 164 | 165 | ```json 166 | "response": { 167 | "status": 200, 168 | "statusText": "OK", 169 | "httpVersion": "HTTP/1.1", 170 | "headers": [], 171 | "content": {}, 172 | "headersSize": 87, 173 | "bodyCaptured": true, 174 | "bodySize": 25 175 | } 176 | ``` 177 | 178 | | name | type | required | default | description | 179 | | ----------------------- | --------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | 180 | | **status** | `Number` | `✔`️ | `N/A` | [Response status][rfc7231-status] | 181 | | **statusText** | `String` | `✔`️ | `N/A` | [Response status description][rfc7231-status] | 182 | | **httpVersion** | `String` | `✔`️ | `N/A` | [Response HTTP Version][rfc7230-version] | 183 | | **headersSize** | `Number` | `✔`️ | `0` | Total number of bytes from the start of the HTTP response message until (and including) the double `CRLF` before the body | 184 | | **bodyCaptured** | `Boolean` | `✔`️ | `false` | Indicates whether response body was available and could be transmitted as `content.text`, regardless of whether the body is actually transmitted in the ALF. | 185 | | **bodySize** | `Number` | `✔`️ | `0` | Size of the received response body in bytes. *Set to `0` in case of responses coming from the cache (e.g. `304`)* | 186 | | [**headers**](#headers) | `Array` | `✔`️ | `N/A` | List of [`header`](#headers) objects | 187 | | [**content**](#content) | `Object` | `✔`️ | `N/A` | Details about the response body | 188 | 189 | #### Notes 190 | 191 | ##### bodyCaptured 192 | 193 | This value is to determine that after the `response` is sent the **mechanism** (Manual request, Agent request, etc) sending information had the **body data** available and could be sent in the case that the mechanism chose not to. 194 | 195 | ##### Calculating Total Request Size 196 | 197 | The total request size sent can be computed as follows *(if both values are available)*: 198 | 199 | ```js 200 | // JavaScript Psuedocode 201 | let totalRequestSize = entry.request.headersSize + entry.request.bodySize 202 | ``` 203 | 204 | --- 205 | 206 | 207 | ### headers 208 | 209 | This object contains list of all headers (used in [`request`](#request) and [`response`](#response) objects). 210 | 211 | ```json 212 | "headers": [ 213 | { "name": "accept", "value": "*/*" }, 214 | { "name": "content-length", "value": "25" }, 215 | { "name": "content-type", "value": "application/json" } 216 | ] 217 | ``` 218 | 219 | | name | type | required | default | description | 220 | | --------- | -------- | -------- | ------- | ------------------------------------------------------------------ | 221 | | **name** | `String` | `✔`️ | `N/A` | The name of the header | 222 | | **value** | `String` | `✔`️ | `N/A` | The header value | 223 | 224 | 225 | --- 226 | 227 | ### queryString 228 | 229 | This object contains list of all parameters & values parsed from a query string, if any (embedded in [`request`](#request) object). 230 | 231 | ```json 232 | "queryString": [ 233 | { "name": "foo", "value": "bar" }, 234 | { "name": "baz", "value": "hey" } 235 | ] 236 | ``` 237 | 238 | | name | type | required | default | description | 239 | | --------- | -------- | -------- | ------- | ------------------------------------------------------------------ | 240 | | **name** | `String` | `✔`️ | `N/A` | The name of the query | 241 | | **value** | `String` | `✔`️ | `N/A` | The query value | 242 | 243 | 244 | --- 245 | 246 | ### postData 247 | 248 | This object describes posted data, if any (embedded in [`request`](#request) object). 249 | 250 | ```json 251 | "postData": { 252 | "mimeType": "application/json", 253 | "text": "W2JpbmFyeSBkYXRhXQ==", 254 | "encoding": "base64" 255 | } 256 | ``` 257 | 258 | | name | type | required | default | description | 259 | | ------------ | -------- | -------- | ------- | ------------------------------------------------------------- | 260 | | **mimeType** | `String` | `✔`️ | `N/A` | [Mime type][mime-type] of posted data | 261 | | **encoding** | `String` | `✔`️ | `plain` | Encoding used for response `text` field e.g "base64" | 262 | | **text** | `String` | `✔`️ | `N/A` | Plain text posted data | 263 | 264 | > - The `text` field is populated with textual content only 265 | > - The `text` field is either HTTP decoded text or a encoded (e.g. "base64") representation of the message body 266 | > - `encoding` field is useful for including binary, or compressed data (e.g. images, gzip). 267 | > - Leave out the entire `postData` object if the message body was not captured, or sent. 268 | 269 | --- 270 | 271 | ### content 272 | 273 | This object describes details about response content (embedded in [`response`](#response) object). 274 | 275 | ```json 276 | "content": { 277 | "mimeType": "text/html; charset=utf-8", 278 | "text": "\n" 279 | } 280 | ``` 281 | 282 | | name | type | required | default | description | 283 | | ------------ | -------- | -------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | 284 | | **mimeType** | `String` | `✔`️ | `N/A` | MIME type of the response `text` *(value of the `Content-Type` response header)*. The charset attribute of the MIME type is included *(if available)* | 285 | | **encoding** | `String` | `✔`️ | `N/A` | Encoding used for response `text` field e.g "base64" | 286 | | **text** | `String` | `✖`️ | `N/A` | Response body sent from the server | 287 | 288 | > - The `text` field is populated with textual content only 289 | > - The `text` field is either HTTP decoded text or a encoded (e.g. "base64") representation of the response body 290 | > - `encoding` field is useful for including binary, or compressed data (e.g. images, gzip). 291 | > - Leave out the entire `content` object if the message body was not captured, or sent. 292 | 293 | ###### Example 294 | 295 | > **original** 296 | 297 | > ```html 298 | > \n 299 | > ``` 300 | 301 | > **encoded** 302 | > ```json 303 | > "content": { 304 | > "mimeType": "text/html; charset=utf-8", 305 | > "text": "PGh0bWw+PGhlYWQ+PC9oZWFkPjxib2R5Lz48L2h0bWw+XG4=", 306 | > "encoding": "base64" 307 | > } 308 | > ``` 309 | 310 | 311 | --- 312 | 313 | ### timings 314 | 315 | This object describes various phases within request-response round trip. All times are specified in milliseconds. 316 | 317 | ```json 318 | "timings": { 319 | "blocked": 0, 320 | "connect": 15, 321 | "send": 20, 322 | "wait": 38, 323 | "receive": 12 324 | } 325 | ``` 326 | 327 | | name | type | required | default | description | 328 | | ----------- | -------- | -------- | ------- | ------------------------------------------------------------------- | 329 | | **blocked** | `Number` | `✖`️ | `-1` | Time spent in a queue waiting for a network connection | 330 | | **connect** | `Number` | `✖`️ | `-1` | Time required to create TCP connection | 331 | | **send** | `Number` | `✔`️ | `N/A` | Time required to send HTTP request to the server | 332 | | **wait** | `Number` | `✔`️ | `N/A` | Waiting for a response from the server | 333 | | **receive** | `Number` | `✔`️ | `N/A` | Time required to read entire response from the server (or cache) | 334 | 335 | > - All times are specified in **milliseconds**. 336 | > - The `send`, `wait` and `receive` timings are not optional and **must** have non-negative values. 337 | > - Tools that can provide these timings can set their values to `-1` if they don't apply. For example, connect would be `-1` for requests which re-use an existing connection. 338 | > - The `time` value for the request must be equal to the sum of the timings supplied in this section *(excluding any `-1` values)*. 339 | > - The Following must be true in case there are no `-1` values (`entry` is an object in [`log.entries`](#entries)) : 340 | > ```js 341 | > entry.time == entry.timings.blocked + entry.timings.connect 342 | > entry.timings.send + entry.timings.wait + entry.timings.receive 343 | > ``` 344 | 345 | --- 346 | 347 | ## Custom Fields 348 | 349 | The specification **DOES NOT** allow adding custom or additional fields. 350 | 351 | ## Versioning Scheme 352 | 353 | The spec follows [Semantic Versioning][semver] 354 | 355 | Given a version number: `MAJOR.MINOR.PATCH`, the: 356 | 357 | - `MAJOR` version increment indicates incompatible API changes 358 | - `MINOR` version increment indicates added functionality in a backwards-compatible manner 359 | - `PATCH` version increment indicates backwards-compatible bug fixes. 360 | 361 | ## What's New 362 | 363 | Summary of changes from `v1.0.0`: 364 | 365 | ###### Changed 366 | - [`request.bodySize`](#request) no longer support `-1` values 367 | - [`request.headersSize`](#request) no longer support `-1` values 368 | - [`response.bodySize`](#response) no longer support `-1` values 369 | - [`response.headersSize`](#response) no longer support `-1` values 370 | 371 | ###### Relocated 372 | - `clientIPAddress` > [`entry.clientIPAddress`](#entries) 373 | 374 | ###### New 375 | - [`request.bodyCaptured`](#request) 376 | - [`response.bodyCaptured`](#response) 377 | - [`request.postData.encoding`](#content) 378 | 379 | ###### Removed 380 | 381 | - `log.browser` 382 | - `log.pages` 383 | - `log.version` 384 | - `entry.cache` 385 | - `entry.connection` 386 | - `entry.pageref` 387 | - `request.cookies` 388 | - `request.postData.params` 389 | - `response.content.compression` 390 | - `response.content.size` 391 | - `response.cookies` 392 | - `response.redirectURL` 393 | - `timings.dns` 394 | - `timings.ssl` 395 | - all `comment` properties 396 | 397 | --- 398 | 399 | ###### Full Example 400 | 401 | ```json 402 | { 403 | "version": "1.1.0", 404 | "serviceToken": "", 405 | "environment": "PRODUCTION", 406 | "har": { 407 | "log": { 408 | "creator": { 409 | "name": "HAR Logger", 410 | "version": "1.0.0" 411 | }, 412 | "entries": [ 413 | { 414 | "startedDateTime": "2016-03-13T03:47:16.937Z", 415 | "serverIPAddress": "10.10.10.10", 416 | "clientIPAddress": "10.10.10.20", 417 | "time": 82, 418 | "request": { 419 | "method": "POST", 420 | "url": "https://mockbin.org/request", 421 | "httpVersion": "HTTP/1.1", 422 | "queryString": [ 423 | { "name": "foo", "value": "bar" }, 424 | { "name": "baz", "value": "abc" } 425 | ], 426 | "headers": [ 427 | { "name": "accept", "value": "*/*" }, 428 | { "name": "content-length", "value": "25" }, 429 | { "name": "content-type", "value": "application/json" } 430 | ], 431 | "headersSize": 62, 432 | "bodyCaptured": true, 433 | "bodySize": 25, 434 | "postData": { 435 | "mimeType": "application/json", 436 | "text": "W2JpbmFyeSBkYXRhXQ==", 437 | "encoding": "base64" 438 | } 439 | }, 440 | "response": { 441 | "status": 200, 442 | "statusText": "OK", 443 | "httpVersion": "HTTP/1.1", 444 | "headers": [ 445 | { "name": "content-length", "value": "25" }, 446 | { "name": "content-type", "value": "application/json; charset=utf-8" }, 447 | { "name": "x-powered-by", "value": "mockbin" } 448 | ], 449 | "content": { 450 | "mimeType": "application/json", 451 | "text": "{\"foo\": \"bar\"}", 452 | }, 453 | "headersSize": 87, 454 | "bodyCaptured": true, 455 | "bodySize": 25 456 | }, 457 | "cache": { 458 | "beforeRequest": null, 459 | "afterRequest": { 460 | "expires": "2009-04-16T15:50:36", 461 | "lastAccess": "2009-16-02T15:50:34", 462 | "eTag": "", 463 | "hitCount": 0, 464 | } 465 | }, 466 | "timings": { 467 | "blocked": -1, 468 | "connect": -1, 469 | "send": 0.06, 470 | "wait": 87.26, 471 | "receive": 0.24 472 | } 473 | } 474 | ] 475 | } 476 | } 477 | } 478 | ``` 479 | 480 | [galileo]: https://getgalileo.io/ "Galileo" 481 | [har-spec]: https://github.com/ahmadnassri/har-spec/blob/master/versions/1.2.md "Har 1.2 Specification" 482 | [iso-8601]: http://en.wikipedia.org/wiki/ISO_8601 "ISO 8601" 483 | [mime-type]: http://httpwg.github.io/specs/rfc7231.html#media.type 484 | [rfc7230-uri]: http://httpwg.org/specs/rfc7230.html#uri "RFC7230" 485 | [rfc7230-version]: http://httpwg.github.io/specs/rfc7230.html#http.version "RFC7230" 486 | [rfc7231-methods]: http://httpwg.github.io/specs/rfc7231.html#methods "RFC7231" 487 | [rfc7231-status]: http://httpwg.github.io/specs/rfc7231.html#status.codes "RFC7231" 488 | [rfc7232-etag]: http://httpwg.org/specs/rfc7232.html#header.etag 489 | [rfc7540-headers]: http://httpwg.org/specs/rfc7540.html#HeaderBlock 490 | [semver]: http://semver.org/ "Semantic Versioning" 491 | -------------------------------------------------------------------------------- /versions/2.0.0.md: -------------------------------------------------------------------------------- 1 | # API Log Format (ALF) v2.0.0 *(Proposed)* 2 | 3 | ## Data Structure 4 | 5 | Log messages are **REQUIRED** to be sent in `UTF-8` encoding, other encodings are forbidden. 6 | 7 | Summary of HAR object types: 8 | 9 | - [/](#-root) 10 | - [creator](#creator) 11 | - [service](#service) 12 | - [entries](#entries) 13 | - [timings](#timings) 14 | - [request](#request) 15 | - [headers](#headers) 16 | - [queryString](#querystring) 17 | - [content](#content) 18 | - [response](#response) 19 | - [headers](#headers) 20 | - [content](#content) 21 | 22 | --- 23 | 24 | ### `/` *(root)* 25 | 26 | This object represents the root of exported data. 27 | 28 | ```json 29 | { 30 | "version" : "2.0.0", 31 | "creator" : {}, 32 | "service" : {}, 33 | "entries": [] 34 | } 35 | ``` 36 | 37 | | name | type | required | default | description | 38 | | ----------------------- | --------- | --------- | ------- | ---------------------------------------------------------------- | 39 | | **version** | `String` | ✔️ | `2.0.0` | Version number of the format | 40 | | [**creator**](#creator) | `Object` | ✔️ | `N/A` | Name and version info of the log [creator](#creator) application | 41 | | [**service**](#entries) | `Object` | ✖️ | `N/A` | Service data (e.g. [Galileo][galileo]) | 42 | | [**entries**](#entries) | `Array` | ✔️ | `N/A` | List of all exported (tracked) [requests](#entries) | 43 | 44 | ### service 45 | 46 | ```json 47 | "service": { 48 | "token": "", 49 | "environment": "" 50 | } 51 | ``` 52 | 53 | | name | type | required | description | 54 | | --------------- | --------- | --------- | --------------------------------------------------- | 55 | | **token** | `String` | ✔️ | Service Token *(e.g. [Galileo Analytics][galileo])* | 56 | | **environment** | `String` | ✖️ | server environment name | 57 | 58 | --- 59 | 60 | ### creator 61 | 62 | ```json 63 | "creator": { 64 | "name": "galileo-agent-node", 65 | "version": "1.0.0" 66 | } 67 | ``` 68 | 69 | | name | type | required | default | description | 70 | | ----------- | --------- | --------- | ------- | ------------------------------------------------- | 71 | | **name** | `String` | ✔️ | `N/A` | Name of the application used to export the log | 72 | | **version** | `String` | ✔️ | `N/A` | Version of the application used to export the log | 73 | 74 | --- 75 | 76 | ### entries 77 | 78 | This object represents an array with all exported HTTP requests. 79 | 80 | ```json 81 | "entries": [ 82 | { 83 | "startedDateTime": "2016-03-13T03:47:16.937Z", 84 | "serverIPAddress": "10.10.10.10", 85 | "clientIPAddress": "10.10.10.20", 86 | "time": 82, 87 | "request": {}, 88 | "response": {}, 89 | "timings": {} 90 | } 91 | ] 92 | ``` 93 | 94 | | name | type | required | default | description | 95 | | ------------------------- | --------- | --------- | ------- | ----------------------------------------------------------------------- | 96 | | **startedDateTime** | `String` | ✔️ | `N/A` | Date and time stamp of the request start ([`ISO 8601`][iso-8601]) | 97 | | **serverIPAddress** | `String` | ✖️ | `N/A` | IP address of the server that was connected (result of DNS resolution) | 98 | | **clientIPAddress** | `String` | ✖️ | `N/A` | IP address of the client originating the request | 99 | | **time** | `Number` | ✔️ | `0` | Total elapsed time of the request in milliseconds. | 100 | | [**request**](#request) | `Object` | ✔️ | `N/A` | Detailed info about the request | 101 | | [**response**](#response) | `Object` | ✔️ | `N/A` | Detailed info about the response | 102 | | [**timings**](#timings) | `Object` | ✔️ | `N/A` | Detailed timing info about request/response round trip | 103 | 104 | > `time`: is the sum of all timings available in the [timings](#timings) object 105 | 106 | --- 107 | 108 | ### request 109 | 110 | This object contains detailed info about performed request. 111 | 112 | ```json 113 | "request": { 114 | "httpVersion": "HTTP/1.1", 115 | "method": "POST", 116 | "url": "https://mockbin.org/request", 117 | "headersSize": 62, 118 | "bodyCaptured": true, 119 | "bodySize": 25, 120 | "queryString" : [], 121 | "headers": [], 122 | "content" : {} 123 | } 124 | ``` 125 | 126 | | name | type | required | default | description | 127 | | ------------------------------- | --------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------ | 128 | | **httpVersion** | `String` | ✖️ | `N/A` | [Request HTTP Version][rfc7230-version] | 129 | | **method** | `String` | ✔️ | `N/A` | [Request method][rfc7231-methods] | 130 | | **url** | `String` | ✔️ | `N/A` | Absolute [URL][rfc7230-uri] of the request *(query & fragments are not included)* | 131 | | **headersSize** | `Number` | ✔️ | `0` | Total number of bytes from the start of the HTTP request message until (and including) the double `CRLF` before the body | 132 | | **bodyCaptured** | `Boolean` | ✔️ | `false` | Indicates whether the request body info was captured | 133 | | **bodySize** | `Number` | ✔️ | `0` | Size of the request body in bytes (e.g. `POST` payload) | 134 | | [**queryString**](#querystring) | `Array` | ✖ | `N/A` | List of query parameter [objects](#querystring) | 135 | | [**headers**](#headers) | `Array` | ✔️ | `N/A` | List of [`header`](#headers) objects | 136 | | [**content**](#content) | `Object` | ✖️ | `N/A` | Details about the request body | 137 | 138 | > - `bodyCaptured` indicates whether the `bodySize` zero value is due to an empty body, or a failure to capture body info by the logging agent 139 | > - The total request size sent can be computed as follows *(if both values are available)*: 140 | > 141 | > ```js 142 | > let totalSize = entry.request.headersSize + entry.request.bodySize 143 | > ``` 144 | 145 | --- 146 | 147 | ### response 148 | 149 | This object contains detailed info about the response. 150 | 151 | ```json 152 | "response": { 153 | "httpVersion": "HTTP/1.1", 154 | "status": 200, 155 | "statusText": "OK", 156 | "headersSize": 87, 157 | "bodyCaptured": true, 158 | "bodySize": 25, 159 | "headers": [], 160 | "content": {} 161 | } 162 | ``` 163 | 164 | | name | type | required | default | description | 165 | | ----------------------- | --------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------- | 166 | | **httpVersion** | `String` | ✖️ | `N/A` | [Response HTTP Version][rfc7230-version] | 167 | | **status** | `Number` | ✔️ | `N/A` | [Response status][rfc7231-status] | 168 | | **statusText** | `String` | ✔️ | `N/A` | [Response status description][rfc7231-status] | 169 | | **headersSize** | `Number` | ✔️ | `0` | Total number of bytes from the start of the HTTP response message until (and including) the double `CRLF` before the body | 170 | | **bodyCaptured** | `Boolean` | ✔️ | `false` | Indicates whether the request body info was captured | 171 | | **bodySize** | `Number` | ✔️ | `0` | Size of the received response body in bytes. *Set to `0` in case of responses coming from the cache (e.g. `304`)* | 172 | | [**headers**](#headers) | `Array` | ✔️ | `N/A` | List of [`header`](#headers) objects | 173 | | [**content**](#content) | `Object` | ✖️ | `N/A` | Details about the response body | 174 | 175 | > - `bodyCaptured` indicates whether the `bodySize` zero value is due to an empty body, or a failure to capture body info by the logging agent 176 | > - The total response size received can be computed as follows *(if both values are available)*: 177 | > ```js 178 | > let totalSize = entry.response.headersSize + entry.response.bodySize 179 | > ``` 180 | 181 | --- 182 | 183 | ### headers 184 | 185 | This object contains list of all headers (used in [`request`](#request) and [`response`](#response) objects). 186 | 187 | ```json 188 | "headers": [ 189 | { "name": "accept", "value": "*/*" }, 190 | { "name": "content-length", "value": "25" }, 191 | { "name": "content-type", "value": "application/json" } 192 | ] 193 | ``` 194 | 195 | | name | type | required | default | description | 196 | | ---------- | --------- | --------- | ------- | ---------------------- | 197 | | **name** | `String` | ✔️ | `N/A` | The name of the header | 198 | | **value** | `String` | ✔️ | `N/A` | The header value | 199 | 200 | --- 201 | 202 | ### queryString 203 | 204 | This object contains list of all parameters & values parsed from a query string, if any (embedded in [`request`](#request) object). 205 | 206 | ```json 207 | "queryString": [ 208 | { "name": "foo", "value": "bar" }, 209 | { "name": "baz", "value": "hey" } 210 | ] 211 | ``` 212 | 213 | | name | type | required | default | description | 214 | | --------- | --------- | --------- | ------- | --------------------- | 215 | | **name** | `String` | ✔️ | `N/A` | The name of the query | 216 | | **value** | `String` | ✔️ | `N/A` | The query value | 217 | 218 | --- 219 | 220 | ### content 221 | 222 | This object describes details about message body (embedded in [`request`](#request) and [`response`](#response) objects). 223 | 224 | ```json 225 | "content": { 226 | "text": "W2JpbmFyeSBkYXRhXQ==", 227 | "encoding": "base64" 228 | } 229 | ``` 230 | 231 | | name | type | required | default | description | 232 | | --------------- | --------- | -------- | ------- | ---------------------------------------------------- | 233 | | **text** | `String` | ✔️️ | `N/A` | Response body sent from the server. | 234 | | **encoding** | `String` | ✔️ | `plain` | Encoding used for response `text` field e.g "base64" | 235 | 236 | > - `encoding` field is useful for including binary responses (e.g. images). 237 | > - The `text` field is populated with textual content only 238 | > - The `text` field is either HTTP decoded text or a encoded (e.g. "base64") representation of the message body 239 | > - Leave out the entire `content` object if the message body was not captured, or sent. 240 | 241 | ###### Example 242 | 243 | > **original** 244 | 245 | > ```html 246 | > \n 247 | > ``` 248 | 249 | > **encoded** 250 | > ```json 251 | > "content": { 252 | > "text": "PGh0bWw+PGhlYWQ+PC9oZWFkPjxib2R5Lz48L2h0bWw+XG4=", 253 | > "encoding": "base64" 254 | > } 255 | > ``` 256 | 257 | --- 258 | 259 | ### timings 260 | 261 | This object describes various phases within request-response round trip. 262 | 263 | ```json 264 | "timings": { 265 | "send": 0.06, 266 | "wait": 87.26, 267 | "receive": 0.24 268 | } 269 | ``` 270 | 271 | | name | type | required | default | description | 272 | | ------------ | --------- | --------- | ------- | ---------------------------------------------------- | 273 | | **send** | `Number` | ✔️ | `N/A` | Time required to send HTTP request to the server | 274 | | **wait** | `Number` | ✔️ | `N/A` | Waiting for a response from the server | 275 | | **receive** | `Number` | ✔️ | `N/A` | Time required to read entire response from the server | 276 | 277 | > - All times are specified in **milliseconds**. 278 | > - The `time` value for the [request](#request) must be equal to the sum of the timings supplied in this section. 279 | 280 | --- 281 | 282 | ## Custom Fields 283 | 284 | The specification **DOES NOT** allow adding custom or additional fields. 285 | 286 | ## Versioning Scheme 287 | 288 | The spec follows [Semantic Versioning][semver] 289 | 290 | Given a version number: `MAJOR.MINOR.PATCH`, the: 291 | 292 | - `MAJOR` version increment indicates incompatible API changes 293 | - `MINOR` version increment indicates added functionality in a backwards-compatible manner 294 | - `PATCH` version increment indicates backwards-compatible bug fixes. 295 | 296 | ## What's New 297 | 298 | Summary of changes from `v1.1.0`: 299 | 300 | ###### Relocated 301 | - `environment` > [`service.environment`](#service) 302 | - `serviceToken` > [`service.token`](#service) 303 | - `har.log.creator` > [`creator`](#creator) 304 | - `har.log.entries` > [`entries`](#entries) 305 | 306 | ###### Renamed 307 | - `request.postData` > [`request.content`](#content) 308 | 309 | ###### New 310 | - [`service`](#service) 311 | 312 | ###### Removed 313 | - `har` 314 | - `har.log` 315 | - `request.postData.mimeType` 316 | - `response.content.mimeType` 317 | - `timings.blocked` 318 | - `timings.connect` 319 | 320 | --- 321 | 322 | ## Full Example 323 | 324 | ```json 325 | { 326 | "version": "2.0.0", 327 | "creator": { 328 | "name": "galileo-agent-node", 329 | "version": "1.0.0" 330 | }, 331 | "service": { 332 | "token": "", 333 | "environment": "PRODUCTION" 334 | }, 335 | "entries": [ 336 | { 337 | "startedDateTime": "2016-03-13T03:47:16.937Z", 338 | "serverIPAddress": "10.10.10.10", 339 | "clientIPAddress": "10.10.10.20", 340 | "time": 82, 341 | "request": { 342 | "method": "POST", 343 | "url": "https://mockbin.org/request", 344 | "httpVersion": "HTTP/1.1", 345 | "queryString": [ 346 | { "name": "foo", "value": "bar" }, 347 | { "name": "baz", "value": "hey" } 348 | ], 349 | "headers": [ 350 | { "name": "accept", "value": "*/*" }, 351 | { "name": "content-length", "value": "25" }, 352 | { "name": "content-type", "value": "application/json" } 353 | ], 354 | "headersSize": 62, 355 | "bodyCaptured": true, 356 | "bodySize": 25, 357 | "content": { 358 | "text": "{\"foo\":\"bar\",\"baz\":\"hey\"}", 359 | "encoding": "plain" 360 | } 361 | }, 362 | "response": { 363 | "status": 200, 364 | "statusText": "OK", 365 | "httpVersion": "HTTP/1.1", 366 | "headers": [ 367 | { "name": "content-length", "value": "25" }, 368 | { "name": "content-type", "value": "application/json; charset=utf-8" }, 369 | { "name": "x-powered-by", "value": "mockbin" } 370 | ], 371 | "content": { 372 | "text": "eyJmb28iOiJiYXIiLCJiYXoiOiJoZXkifQ==", 373 | "encoding": "base64" 374 | }, 375 | "headersSize": 87, 376 | "bodyCaptured": true, 377 | "bodySize": 25 378 | }, 379 | "timings": { 380 | "send": 0.06, 381 | "wait": 87.26, 382 | "receive": 0.24 383 | } 384 | } 385 | ] 386 | } 387 | ``` 388 | 389 | [galileo]: https://getgalileo.io/ "Galileo" 390 | [iso-8601]: http://en.wikipedia.org/wiki/ISO_8601 "ISO 8601" 391 | [rfc7230-uri]: http://httpwg.org/specs/rfc7230.html#uri "RFC7230" 392 | [rfc7230-version]: http://httpwg.github.io/specs/rfc7230.html#http.version "RFC7230" 393 | [rfc7231-methods]: http://httpwg.github.io/specs/rfc7231.html#methods "RFC7231" 394 | [rfc7231-status]: http://httpwg.github.io/specs/rfc7231.html#status.codes "RFC7231" 395 | [semver]: http://semver.org/ "Semantic Versioning" 396 | --------------------------------------------------------------------------------