diff --git a/_meta/fields.common.yml b/_meta/fields.common.yml index 96bd97f3df4..61f2e500123 100644 --- a/_meta/fields.common.yml +++ b/_meta/fields.common.yml @@ -47,37 +47,6 @@ description: > The status code of the http response. - - name: user - type: group - fields: - - - name: username - type: keyword - description: > - The username of the logged in user. - - - name: id - type: keyword - description: > - Identifier of the logged in user. - - - name: email - type: keyword - description: > - Email of the logged in user. - - - name: ip - type: ip - description: > - IP of the user where the event is recorded, typically a web browser. - This is obtained from the X-Forwarded-For header, of which the first entry is the IP of the original client. - This value however might not be necessarily trusted, as it can be forged by a malicious user. - - - name: user-agent - type: text - description: > - Software agent acting in behalf of a user, eg. a web browser / OS combination. - - name: request type: group fields: @@ -360,3 +329,61 @@ type: keyword description: > Address the server is listening on. + + - name: user + type: group + fields: + + - name: name + type: keyword + description: > + The username of the logged in user. + overwrite: true + + - name: id + type: keyword + description: > + Identifier of the logged in user. + overwrite: true + + - name: email + type: keyword + description: > + Email of the logged in user. + overwrite: true + + - name: client + type: group + fields: + + - name: ip + type: ip + description: > + IP of the user where the event is recorded, typically a web browser. + This is obtained from the X-Forwarded-For header, of which the first entry is the IP of the original client. + This value however might not be necessarily trusted, as it can be forged by a malicious user. + overwrite: true + + - name: user_agent + title: User agent + description: > + The user_agent fields normally come from a browser request. They often + show up in web service logs coming from the parsed user agent string. + type: group + overwrite: true + fields: + + - name: original + type: keyword + description: > + Unparsed version of the user_agent. + example: "Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1" + overwrite: true + + multi_fields: + - name: text + type: text + description: > + Software agent acting in behalf of a user, eg. a web browser / OS combination. + overwrite: true + diff --git a/beater/test_approved_es_documents/TestPublishIntegrationErrors.approved.json b/beater/test_approved_es_documents/TestPublishIntegrationErrors.approved.json index c7ac8d5d64c..98f3042f25e 100644 --- a/beater/test_approved_es_documents/TestPublishIntegrationErrors.approved.json +++ b/beater/test_approved_es_documents/TestPublishIntegrationErrors.approved.json @@ -70,11 +70,6 @@ }, "tags": { "organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8" - }, - "user": { - "email": "foo@example.com", - "id": 99, - "username": "foo" } }, "error": { @@ -267,6 +262,10 @@ }, "timestamp": { "us": 1494342245999999 + }, + "user": { + "id": "99", + "name": "foo" } }, { @@ -334,6 +333,11 @@ }, "timestamp": { "us": 1533826745999000 + }, + "user": { + "email": "bar@example.com", + "id": "123", + "name": "bar" } }, { @@ -401,6 +405,11 @@ }, "timestamp": { "us": 1547070053000000 + }, + "user": { + "email": "bar@example.com", + "id": "123", + "name": "bar" } }, { @@ -480,6 +489,11 @@ "id": "1234567890987654", "sampled": true, "type": "request" + }, + "user": { + "email": "bar@example.com", + "id": "123", + "name": "bar" } } ] diff --git a/beater/test_approved_es_documents/TestPublishIntegrationMetricsets.approved.json b/beater/test_approved_es_documents/TestPublishIntegrationMetricsets.approved.json index 7080cd7b1bf..6438321b892 100644 --- a/beater/test_approved_es_documents/TestPublishIntegrationMetricsets.approved.json +++ b/beater/test_approved_es_documents/TestPublishIntegrationMetricsets.approved.json @@ -61,7 +61,12 @@ }, "name": "1234_service-12a3" }, - "short_counter": 227 + "short_counter": 227, + "user": { + "email": "user@mail.com", + "id": "axb123hg", + "name": "logged-in-user" + } }, { "@metadata": { @@ -102,6 +107,11 @@ "name": "ecmascript" }, "name": "1234_service-12a3" + }, + "user": { + "email": "user@mail.com", + "id": "axb123hg", + "name": "logged-in-user" } } ] diff --git a/beater/test_approved_es_documents/TestPublishIntegrationTransactions.approved.json b/beater/test_approved_es_documents/TestPublishIntegrationTransactions.approved.json index 21a6350f40a..d0fd6e819f4 100644 --- a/beater/test_approved_es_documents/TestPublishIntegrationTransactions.approved.json +++ b/beater/test_approved_es_documents/TestPublishIntegrationTransactions.approved.json @@ -75,6 +75,11 @@ "started": 43 }, "type": "request" + }, + "user": { + "email": "bar@user.com", + "id": "123user", + "name": "bar" } }, { @@ -157,11 +162,6 @@ "tag2": 12, "tag3": 12.45, "tag4": false - }, - "user": { - "email": "foo@example.com", - "id": "99", - "username": "foo" } }, "host": { @@ -227,6 +227,10 @@ "started": 17 }, "type": "request" + }, + "user": { + "id": "99", + "name": "foo" } }, { @@ -319,6 +323,11 @@ "started": 436 }, "type": "request" + }, + "user": { + "email": "bar@user.com", + "id": "123user", + "name": "bar" } } ] diff --git a/decoder/req_decoder.go b/decoder/req_decoder.go index 2aff42c2beb..637286b863e 100644 --- a/decoder/req_decoder.go +++ b/decoder/req_decoder.go @@ -151,7 +151,6 @@ func DecodeSourcemapFormData(req *http.Request) (map[string]interface{}, error) if err != nil { return nil, err } - payload := map[string]interface{}{ "sourcemap": string(sourcemapBytes), "service_name": req.FormValue("service_name"), diff --git a/docs/data/elasticsearch/generated/errors.json b/docs/data/elasticsearch/generated/errors.json index 0a1c39f5081..8384558a684 100644 --- a/docs/data/elasticsearch/generated/errors.json +++ b/docs/data/elasticsearch/generated/errors.json @@ -65,11 +65,6 @@ }, "tags": { "organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8" - }, - "user": { - "email": "foo@example.com", - "id": 99, - "username": "foo" } }, "error": { @@ -255,6 +250,10 @@ }, "timestamp": { "us": 1494342245999999 + }, + "user": { + "id": "99", + "name": "foo" } }, { @@ -310,6 +309,11 @@ }, "timestamp": { "us": 1533826745999000 + }, + "user": { + "email": "bar@example.com", + "id": "123", + "name": "bar" } }, { @@ -365,6 +369,11 @@ }, "timestamp": { "us": 1533117600000000 + }, + "user": { + "email": "bar@example.com", + "id": "123", + "name": "bar" } }, { @@ -432,6 +441,11 @@ "id": "1234567890987654", "sampled": true, "type": "request" + }, + "user": { + "email": "bar@example.com", + "id": "123", + "name": "bar" } } ] diff --git a/docs/data/elasticsearch/generated/metricsets.json b/docs/data/elasticsearch/generated/metricsets.json index f8ff05fa38e..0ebf60f1c3b 100644 --- a/docs/data/elasticsearch/generated/metricsets.json +++ b/docs/data/elasticsearch/generated/metricsets.json @@ -49,7 +49,12 @@ }, "name": "1234_service-12a3" }, - "short_counter": 227 + "short_counter": 227, + "user": { + "email": "user@mail.com", + "id": "axb123hg", + "name": "logged-in-user" + } }, { "@timestamp": "2017-05-30T18:53:42.281Z", @@ -78,6 +83,11 @@ "name": "ecmascript" }, "name": "1234_service-12a3" + }, + "user": { + "email": "user@mail.com", + "id": "axb123hg", + "name": "logged-in-user" } } ] diff --git a/docs/data/elasticsearch/generated/transactions.json b/docs/data/elasticsearch/generated/transactions.json index 7f03829b29c..6048d4f379b 100644 --- a/docs/data/elasticsearch/generated/transactions.json +++ b/docs/data/elasticsearch/generated/transactions.json @@ -63,6 +63,11 @@ "started": 43 }, "type": "request" + }, + "user": { + "email": "bar@user.com", + "id": "123user", + "name": "bar" } }, { @@ -140,11 +145,6 @@ "tag2": 12, "tag3": 12.45, "tag4": false - }, - "user": { - "email": "foo@example.com", - "id": "99", - "username": "foo" } }, "host": { @@ -203,6 +203,10 @@ "started": 17 }, "type": "request" + }, + "user": { + "id": "99", + "name": "foo" } }, { @@ -283,6 +287,11 @@ "started": 436 }, "type": "request" + }, + "user": { + "email": "bar@user.com", + "id": "123user", + "name": "bar" } } ] diff --git a/docs/fields.asciidoc b/docs/fields.asciidoc index ab132df6c62..35c5222bff0 100644 --- a/docs/fields.asciidoc +++ b/docs/fields.asciidoc @@ -91,57 +91,6 @@ The status code of the http response. -- -*`context.user.username`*:: -+ --- -type: keyword - -The username of the logged in user. - - --- - -*`context.user.id`*:: -+ --- -type: keyword - -Identifier of the logged in user. - - --- - -*`context.user.email`*:: -+ --- -type: keyword - -Email of the logged in user. - - --- - -*`context.user.ip`*:: -+ --- -type: ip - -IP of the user where the event is recorded, typically a web browser. This is obtained from the X-Forwarded-For header, of which the first entry is the IP of the original client. This value however might not be necessarily trusted, as it can be forged by a malicious user. - - --- - -*`context.user.user-agent`*:: -+ --- -type: text - -Software agent acting in behalf of a user, eg. a web browser / OS combination. - - --- - - [float] == url fields @@ -559,6 +508,77 @@ type: keyword Address the server is listening on. +-- + + +*`user.name`*:: ++ +-- +type: keyword + +The username of the logged in user. + + +-- + +*`user.id`*:: ++ +-- +type: keyword + +Identifier of the logged in user. + + +-- + +*`user.email`*:: ++ +-- +type: keyword + +Email of the logged in user. + + +-- + + +*`client.ip`*:: ++ +-- +type: ip + +IP of the user where the event is recorded, typically a web browser. This is obtained from the X-Forwarded-For header, of which the first entry is the IP of the original client. This value however might not be necessarily trusted, as it can be forged by a malicious user. + + +-- + +[float] +== user_agent fields + +The user_agent fields normally come from a browser request. They often show up in web service logs coming from the parsed user agent string. + + + +*`user_agent.original`*:: ++ +-- +type: keyword + +example: Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1 + +Unparsed version of the user_agent. + + +*`user_agent.original.text`*:: ++ +-- +type: text + +Software agent acting in behalf of a user, eg. a web browser / OS combination. + + +-- + -- [[exported-fields-apm-error]] diff --git a/include/fields.go b/include/fields.go index 010b9a935ec..24f96149e61 100644 --- a/include/fields.go +++ b/include/fields.go @@ -31,5 +31,5 @@ func init() { // Asset returns asset data func Asset() string { - return "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" + return "eJzsvftz3LjRKPq7/wpcbX1Xds6Ielh+rE7l5Ci2d1e1tlefZX+bfElKgyExM1iRABcANZ7Nyf9+C40HARIczUgar3OPnKqszSGBRqPR6HfvoSuyPEEkl48QUlSV5AS9eXXxCKGCyFzQWlHOTtD/eoQQ0j+gKSVlIbNHyP7t5BH8tIcYrsgJ2vnfilZEKlzVO/ADQmpZkxNUYEXsg5Jck/IE5Vy4J4L82lBBihOkROMeks+4qjU8O0cHh8/3Dp7tHT39ePDy5ODZydPj7OWzp//tZkiAqv+8xorsa3DQYk4YUnOCyDVhCnFBZ5RhRYrskX/7Oy5QyWfmFYnUnEpEJXxVDA20wBLNCCNCjzVCmBV+OMaVeZua1wTB4Wwf7IoNFtGUC4TL0k6exThVeCYHUWewe0WWCy6KHub+9vedWvCiyTVu/r4zQn/fIez66O87/7gBd2+pVIhP3cASNZIUSHENDCI4nxtQO5CWeELKm2Dlk19Irrqg/pOw6xPUAjtCuK5LmmMD2ZTzvQkW/1oN9Y9kuX+Ny4agGlMhA3y/wgxNiF8FLgpUEYURZVMuKphEP7f4Rxdz3pQFbGLOmcKUIUakIu3+mlXIDJ2WJYI5JcKCIKm43lYsHeoCIN64xY4Lnl8RMdYUg8ZXL+XYoq6Dz4pIiWfD58YgVJHPPXTu/EDKkqOfuSiLG7a6R/jEzWuJ02LA/KTftD8HKztjiKs5ERrBKMeSJMeJ9yDnLMeKsJYxIFTQ6ZQIfbQsShdzms8BsUofpqkgpFwiSbDI53hSkgydTVHVlIrWZTuMnVci8plKNdLfLt30Oa8mlJECUaY44ox0luNwj2eEObRaxngaPJoJ3tQn6Gg1bj/OiRnIcktPTZatYIQnvFHwT8mnaqFXSpiiajlCdIowW2rosSbDstQEN0IFUeYvXCA+kURc64WazeMMYTTnes1cIIWviEQVwbIRpIpfyBw1SkRZXjYFQX8mGAh6Bm9WeIlwKTkSDdOf2amEzOAegFVlf3DrknPNviYE1bxuSs0O0YKquQYW01JqVqI8LkTDGGUzPap+qMEJFiM03zQbbtnsHNc10Vum1wRk5VcEvFWvk2UW6VPOFeOKhNvglnqiCVWPoElUwwRLBu5b8pkctTBmmgg0/5/SkkwIVhmck9PzdyPN0c3F4MePl2W3F9f1vl4QzUkWEELIcQpOpGEyc8xmBNFpexI0cVCJpP5GzQVvZnP0a0MaPYNcSkUqiUp6RdCPeHqFR+gDKaghilrwnEgZvOhHlY0+TRK95TOpsJwjsyZ0AYjPIrYCFO6Qau96/Xc/mDspmigoZ/55ilOhgatqxdnRf/7LDB2RTxZDETC959lBdrAn8qM0nPr/twHke00qMYTR7x+tKIEBAnucDTOa0WsCFw9m9lPztv15Tsp62pQhXRgSF27RSC04+s7SKKJMKsxyexV1jpnUk+uzFo01aZTmCE2FGcgomqkiSWosDIlSiRghhT58zHLj3nTRgI5wc17pyaeCVx18nE0R48gdMECBOXnuEZ8qwlBJpgqRqlbLLLXZU87T26x3cBvb/HFZd7cZxYQY8ns9AZIKLyXC5UL/x++BvvSlETA8CUyWAX/UN2QWo4x5luWx376/gLHsNBPSvgL8m041kUTDDRNMRCwVzueUkTT67RDpPaDFNnbgE6O/NgTRQt+QU0qE2Q59tAAPj+kULnS49eWTxP54CUwzc8P84fuF2w1g9bRILvklPp4+Ozgo0ksm9ZxURODyMrV48lkRVpDibgh44+a4Cw4MO9LCrahwWS7t5SMRzgWXWlORCgstYGjeMDakTouxv61WIWf6qJ3QYSYvaU+UehU+W0+WOrUDaQ5RkCnIcNgcK8qoolhxQAZGjKgFF1da2GIEtAnDMo2MJMgMiwJuR31LciZHwZvmCp3QggrzAJdoWvIFEiTXipCRAz6+OrfDGc7VQtYDRz/QrwfAwA0gCSvM6xd/fY9qnF8R9Vg+MeMbYboWXPGcl71JjM6p964znQBVmmglxIkhDhlKYCYxAJChC14RL0VomV2/qYio0I5TjrnY0ReTIFMioulZZznSSDf2ZysPmj2cEC8ABnIuTIs0KGzmdrAdPITZ6JiWWNzQmlM1soHlt9ImZRqkXxpmUAzCpxUnrckCJcZpEamlsHY0TS5mS/bgAHvFPDpNdrx9N5EgtSBaYIOr09ziWtOUpMJM0Rykf/JZ2QuffDYnb2TvVSr9ha84uqZ6jfQ30uoKeo1EgP4gqWqwxf7ZFC15I/zoU1yW0qASJA1FZlwsR/old+9IRcsSEabFaEuOvBG5uZsKIpWmAI1HjaQpLUt91upa8FpQrEi5vKWoiItCECm3xR+BrI3OYAnKTmgvOM82qgmdNbyR5dIQrzXn0LKMxpO8ImDPQiWVSu/Z2fkIYVTwSm8CFwijhtHPSGp9XmUI/bXFsbmP4/EUt4qNwAsHmyP6cWYfjA0O0+IFGJRa6aFojJHEqNTjjNZjDdY4MyCOtbpYE1ZYORAILRpS3xWg0GQDN3m95k0evbhij87O/cItdzRblViuNdpoELnwWj46O78+1g/Ozq+ftxs8AH/NhVpzBSVns/XWcM6FWgm9N+DgfBuC0LvTV2sh0YFhiGEbkFgWaCbozP4NekeUoLnswTNZKpJgAuvsihc4Dl8erwfin/VkRo/Wykh43ShubqRA++0TEFwDd4b2aE3KMrOtBW4P1BkJxXwraX0fPeyIWjdA8z3h3nCFtQoixDI0W2Eka5LTKc1RyY2pFglSOnak77jrVswzf7jQcMZmECLotb519XqByYYcMERveNEg1PFBxMhwAEWTp7fOj074Zc1pB+AV+EHoLWczqprC3JwlVvCPWHnzRLD7T7RTcrZzgvZePM2eHx6/fHowQjslVjsn6PhZ9uzg2beHL9G/dlPr0bc7ZYSpy44d46ZV9c/zDWsK7Rl+1oElvedCzdFpRQTNcRrshimx3DrQr8w8MOsArK8ww0USSEFmlLOtw/gBplkF4n82ZELyJB6p+gJIpGolBt9xpgTB5aqNppJf5rz4Ipt9dvET0nMNbfjpis3+EnDaDb8RzL3/fJWCdGi7E8LyrUH8JInYc3Jx8KbRpB0THSFrcDLaEJ+imcCsKbHQFGPdK4KYa6Fj7oPtMtKqN/IZ7kKFuUxywhQRVsudlpwLxJpqQgT4QMC44fRJ2RnagFiier6UVP/FOU9yR8qyB857DuY5/Xq5NO4oyhBuFK/g5poR7tY9sGMTLhVne0X+qGPo4E3RtXO0j9Yzc3xn7tvgGjUSAG/A/0HZVGCpRJOrJnSStIjR+xAZX83jG/wiUyusGbOgDI3HmKE3r46Mm0bfclOi8jmRZu/gzqbB9Mb71MKsL/rYhRj5vaj0ZsYYCD+gaJj1WwlSceXNkog3StKCBHOlocPIumHCIUNPDXxsqS/2eJph26HA+2SnDx1AdoIYcevpyCEB1YJf04KItfRjT40kP7qbEB9d+LBiB4j3EoYubpIfjdAsJyPERcxo6IwqXPKc4K4u4A0A15iWeEJLfZ39xlnCUr9qqY3cI1iqvcP8bis+DcBAv4EO7LwbQJJA6+1mDizG3CRrrWAIxv7K1luAvVluA7Wz+Wd3tFN70One4dHT42fPX7z89gBP8oJMD9ZbxJmFBJ29duQHS4j8DsPwp/1592NJ8qAF19U6wLlf006o22BXHWUVKWhTrQf4O8edAm/VGnDjHOS3e6OJ58+fv3jx4uXLl99+++16gH9subiBBUICxAwz+pt1RRY+dsS6P5ZtwEh8UWshgEJoA8LGcLSnCMNMIcKuqeCsSluc2gvx9OcLDwgtRuh7zmclMfc5+unD9+isMBEYJuwFPFPRUK2HJpgnVOYwZZ7TO2mh83g9icF/FVvIrRm7F+YUWOKd8t4FBxmbsHVnWNMwn4bDgN1UEjflnJS1FpuN2GJuzAmWAdH4OaTT85eaUSnaahsbGpPt19tiAR/M8KjCDM/0jQ481i8j6QUzcV0DfGubPlEPFqJdw7Gfv8Kz7TLNUI6A2bwJwYC2wBJNGloqLxwNAKnwbFswtofFQoiH7sltYqqFotW2ewBE0ZTrgBBFViIfpHh5m/sPkOOCElGXfwUuopiDve79sB4PC75bw4UYeqhATzVG2n0bk7pi0A2ch4brtfHO6Gt2d0U+uwef11fv8wr269/V8TW8hC/v/boZlu25wEIu8+/mBwvZhvMuAd/7ip1hK2DuwfvgEXvwiPVX9eARe/CIrYvEB4/Yg0fswSN2W48Y8UJPlFuK1tYL3xGF98Kb0V+viuvBfqeUlWTC6g2U9ebVhZvX7KANVOSwOokUz9CY5DKzL41NzoiIM0X1pVo1UpkAb9imciA8Vf/5WWtPvzZELCHY1kR4e4WCsoLmRKK9PetGqPDSAaQRLEs6m6tyGR8en6MXrAjGgFUZMEstt1GmyEzYYFhc/KLBNhJbrCHmc1Jhjxt7zw4uCQzFjTBZgvYbKtEhJP9MiMJHKGmbC15oB/WEKgTvGGPfBI/WzvZrLaI5JNTYgGAzPqgrmC3RFWVFphmNXmllgtPNC2oeeD5N3pvempIYv6beRJfqBxHeJteymzBHlSTltHVjarFTjx9hc3235JfK5pja/L4+rEMpsTcBFKTG3gAN7HabCpqcu3M53hsmzNx6dMfVjbm5jwlPrte9jIo317dJTjX0kvIbuGjytOug5DNknAuC5hHVZegUfo2zNJzi42hSLzDIDQWj09ysGrcJnxl62yYmA9dzuaqQr0Arom9h5wHVT/UQ7dc+xZVPwxRnNwh2qZIIMl5cuIMNYWjzSIzWiybEJI04ZRQ7G6FW7EK1dGSsZIk0lAlRC0L0HC4+nRU2PoEIO4FN5zDprnnJpV7JqUP1zWh1ViMuiBYaQA8pYSyTCQD/jJKCNRBphKYzbSO8hiTQorYiFRdLpNkf5BjYgYpOhvJ1UzIijCOetrnK9jWZY6YXCvnKt7vot8q6zl7rrfd2as9/b5E9pm+EPqT3YybW51yPH92sQ4lhM3oNftPuoV/oc+mcylHVBDdiNJa7ekZgTNcD2NMTiG9OmzbXWQhb64iNBtX8aQxvjEdoLBVWRP8Fl1hU4wz9jIU+AJDkPW0gPMpLJ3yqpZURWsSiR11iMCLZeBctPNvCFzjPSa0gG9aGvpjbyUk4I1SXBEtgmNGQ4DzIcdMVlj0hANwDF4zN1dnKJWP4hJ1haPu9yDCns7nNfUrfAAM7dxbTAZWGEUGild72OWZ2DzOTjDYeOWeAJEzabKRWGcExWVnwWzi9LItdMtoaZBBvGLkHMohGbCRJkEGKFhqta4KDGXhsmirMyrZBE5CubG6mHNcKOK/NRF7JJLzuafMPW/qgLCYGTwDtwZ/j2AJpqcFt7Ti4XuDAA6/fw0Whz7q9sPfgwibFON7K8ZSWZC8XRF+fY+PmMvVgqGzzXd39aVdK9VwVKNzJ8wp7VGMpNV73TMpeeqN4o3K+PaexXo2d4iZWfhb8HOwWZna7RwEJyzg6s50hNqboY+nSR9v737xsd0o2eQ6+PChrM8W0bASJGXM05jCT3uRExkMOMuk1T6RdQ3qDt1Va4AMBCdAI3hYrTUIR0X/OzYrwNYd4KB+Y0haS0gQLZqQhFYoXTbn1ShhmFmurStWE6K3MJKaHzCT6IhhVehuVyeHnwlc0SR7hail/LdPI0KBJsq6n9NbYsNMMmTM400RtLIxj++4YPdbsTBKF9q2ULYl6orESr17rAbFBpZnor7RwbtAFnDg65SGaffaxtap07D22ohVlLRCmOg6Yovwju9+agA3UWddsHklAAydMkmsiqFpXAhryMO682Flvjy7sfJ0rzYHREW5+nlujbzrs0H9lRYWKgIuQaQ4XhCp6LdAXy9L7sytRUyPFO1w3up80R6zwFUGgU9npqGW/OWeSSgVapbHzJU1o/rIyef7lrSn/G/RJE5FqGGSEW5umDRenpq6RnPMFM3GBuSqXaEmUJtf/gwpuKuRxcRUNqeUHzdslWpAoMOUbdCbR//vN4dHx/3RxiXG6vd6q/wPV9ri40oDAiQJLRmsjiwY0waQ0v5JJKt25IDU6/BYdvDw5en5yeGDCaF+9+e7kwMBxQfJGb7f5V7Rveue0FGJEO2HeOMzsh4cHB8lvFlxU7gKaNlpUkYrXNSncZ+a/UuR/PDzI9P8OOyMUUv3xKDvMjrIjWas/Hh49PVrzICD0AS/AXuartvEp+A6EJ/9PNvq2IBVnUgmsjCHI2HmpSmkVlq2b28lSBWUF+UyMLbvg+WWQW1BQqbe/MBwLM/36hHRGNOXfSGEqlFBfTUloZkS833x8aewz43B7Ye4TNMVlJLS3YIS/9Q7NHMv5ncS7lrramPnU307//Or12jv3A5Zz9LgmYo5rCZXMoLbXlLIZEbWgTD3Rmynwwu6D4hpdIEN1GA5ae3P9BdqIblTB/cQavbYDRzxYMwiGGZck56xIuQfOppZcQUUAGjP/JqwAErtimicBtzK6QRtZ1vVMOJadE8+zARJmaNfM0EYw9+VFWpG1k1xupRH4o9UuIqjAF1Ur3ZXI12ZtK89Zg11861iwY82/FAQXS/SYZLNM61C4KRW6WEpNJH5g+cTcZdF4vLaFdCBYfkFlSq49beV6P7+ZHTjDCcL6mHMG5suz1xaOnTeN4DXZP62kIqLA1c6TWCXEk4kg18ae6j65+LjzBEy0DP3ww0lVtVczxaV7a+/g2cnBwU63gpI31Rglc02qL8Iilyu31CrDZvRe3lyyAq19eUiibjddS+JUKspya8H+38FvtlxM8MhN3pNIrBIOt6d9OXNlRAFUaerStVThOHRabrI1gDrAGPZTUmYkzc7CqSmpG9bCi8acLIMyaIIYWgdXU47LDI3bdY6NZyGszOl/i7fmsxI4V+56CSEcdfbNA+uXQF0J4Hh/bKW13ETP1rWWozg4HPQNbIwyWgEyHr7E5vR4VvtKAt7Qo6EnaLljF/I+Ud5Aa65EHeAv3nyNf4/7UbiKlmu1Ne/6OoFmsxuw0E0Pm2HjNx41a3LSjCOJJJwreq2lf42nKRVSuYqmQwsjG9n8N12WvqVuXBRMFS7JLyMaUS+pxDevSFB5dSk7LHAVY5yWHK/pof1A5RWCsU2RU8p7Gprl3dIK5kjyEsw9rg6e+/NJElMyy9Qi25VeG7IigT5tNy7xknFRbbCBG6z1Pdgq6W+kgPluWPbIu8tKkNoPNA85PDgYtLFIVGHKTKiPqS8KxcG0PlqZaH3MwI9oa7UZ45+UdNa5DVrgJJQ/h2EW2NSqkYQgbM2usBSDW6uc4rJ0FegSDu4p9fy848y27u7v2heG8HgKo3Q9psiaRmIfFjidJZpoEc+xQuvI1c8h2Ma5JcG+AZBnAIarBe4uOSwlz2lbAxn0RlctMCptZ5C2b20mzocKRDxCas4lsRXRjbUaJjtz8jh6xxlVHK6Hv3139u4frno62MNsRjoUFITwEWPqdfbUfk4Nnk6JuSz06901qKB4vjX6bOSRbQPIVatADR2YtCQcbfM51kBxm7Nfxoe1LZwvZkRd3tecH2E4WAKIHXJZlZRdyeTcMEEUY3aHmUPmALvpR+8dcTjgPhun5AtEsFxqHCkCpDJZWmJzQwTWD6+d1lZJ6yI0tH/fYT2wBnAmg4lzhAoq4KxZlD5JorQgURGHO8z/GkYaSHJdSVKUhTFAdwDhTA/UmrBcwI/hWMz/3fKZFChNENtwT7Sl5VHwHmj96tPZ6yeGk9jbNIjUenwBP7bIQnzBOiXUvKFxESYW35VqYLRdMIGLXu6kT/u4H9ScC1phsTS8DXDyfWfZ6dmjlIx7mz+sRDA4d3V78vSH/+D58UEaoHeaZsNdpwzxXOGyY4tNgibpb+uCFhmJ+jSgR9JTQ/qUZiHWtsi1SIOLwqkxYz3aGNFYZgEn8TjNYqoooXw1kJE8HgH5VkvKEEwFSLKREiBEV7zQJ6hIzp5vY/aKKGxiysFzXSSErZBgXY5U8Gj9aEJDqEE0YUWsLNhGwsI70oqUQrPAklxj1osMjiKp7iHq634sbsNBq2btrnw6sO39usRKS5m/Q4Z56HwE0BL7HjQDsNv+Q/tk3aLcruhMJGPbusoo51XdKBPVaKu2QNQ4RPQFzUMStsuwe0grpZpeISwIUYxbhJiaHOzmEEa9UsBrG7M4x6JYYEFG6JoK1eDS1UyRI/QaCjsERSyMuvNjMyGCEQXG1ILcNk9crypNDHf3Qv9gxw6LwaTMNyooCO+sBgvn7xw7CMd6Syu9dEFUI0xlrjVrzGxrhe/XWh2ka1obH6wrWFOwlk+Q2m70Upt+05Qdj/ivDS6Bi7ukeD2KC/rVwNhgpzbGSEsrJhxJ6rPdKZtFclr4XkdGSVZcfzOUn77NoFZznlMWvlPpCdV58mzPCVP+ZgQGBOvM8/xdXwGUzaZNXGaAMmOBWasez0mU9NE47+QYujXAFmZ9JN13Ej9wDFq71PMvm/P+gz1eN8y+7d4nA8frOy5sZSRXOM721bAWkahsnh4KGheNfWmrcWyeO5ui62rk6u0EmXKe/Y5Cu39Qhykw6kQjtkS4BuH5uEuRz6kiUGjx1khtHb6fXz6/fH68plP3p5oIrNoWThEwiUR3Hsq49jJvx7iAMYI3Nkt614fvp4tuC7N0WDDvAB7urCANePdPotEVry8tTrteeY2+GqxS8Sd7vldY53GvvdEesN7LsJkbuk3uvJPkosG3kHza23c3MXoMvbtywhSXI9RMGqaaEVpQVvBF177d1qPCYkHZFjNpW/J+h3NNJH/ZucNizT3qMgY0OdnY0HB5icXoK3obi3nHf8HX5O4rMhKms/H4REeb82XMGKll4Yp2RI+7LqwgE4rZJiu6sGBYAoRWpsUcqxEyY42gKeNEFiExJhbTT7m9+2oOD7LD4+zwLhvkNgPUFoEXSCoBtTMTS7jSsv79Etpxdpwd7B0eHu3ZDIm7rMXAt8aSHsqjJHb3oTzKQ3mUGNaH8igP5VEeyqN0QHwoj3J/5VHmSnXs7j98/Hhun9y2XYAewkfx3La0rukimFVEzfnWjOk/KFW7qZCZaiBBxrh4jGkMovUmJAwsURyVfEEEBKBNufAVTzJ0QeITsfPWv/gK11TpEWDndpzbdefMJVxo0erNq4sdhKTJ30/mCcyIGqEaMtrrZiCF0+FzwotlZv1B28LqR2uzBOry6IWZU+CbRvELLsqB1HQHO3SCFGs2J7hVEpwZv83hA0p206dg1yuUJ/v7k5LPMvs0y3m1P7QSWXMmSSYVVo3scvObVrN+6LolbDMbMrP1GLpfxfHB8Q3w/h5kY4G/Pd0M1li6R+aRMA8E9X4OY8CG63D645mux3kPFPGRK1x2HNdWYnYn9LFGNWgFc4ILImKjTrus46cv1mAy21vKxapFDJLLy5eDUDsi/32Qb+n8HrAfHtYvjv6bjmuE/1blncXix1v/YLW4YVxVOMrr50GJnVuKHYClPtbu7r94y2etJOri8oeS501Z7agGwc+nH96PR2j85sMH/Z+z99/9NE6i+c2HD+ml3TndcjgvEQRacNu9W+qFhSakjdLdBtHYuShMCDFY+13YtManyxvE3cBzuFaCN6LhJmRq6kOUVJlIAYUaSAHxpT1qLJKV4M6MR1dgX1cOje0Utp64JdTQ9wuNl11iRB1nFqCQPOxIYamETqUEu/hRb4Edd5ZxPs/xNfFZVFLTmAkGyl2BvLouKSmMb4ywnJsC5gIxsoiVOsqIhGZY10b2zUuCGWQPx6APxX9vmoyJJLdZlru9bEwtaYOj2xnsQUZfKyEzYkU2LjpmR++jh+vHIbkg636n+JxXVcMszk0oL78mwjE0G18i4jBtG11iG53bn24VvuKG9bki3ThrZwG9JQPdekTRjF4TffdYPx+ULOROPZKtmu6QlGJg34Ok8DOd0vQituXEPjP63U8XZxDIWJqDvQhtDZbg0Fu8JCJDtL4+Hun/f67/X5J8hGpajRBR+Vepp65SU/Va0vimmOFLYz/ZFu0gdHb6/hSdC654zkv0HmZDj50Ct1gsMg1GxsVs3ySaQGm6/dp+sWfg6z/IPs9VVXb8nwhdKMwKLApAuysd476Fg0wlwiWdMVNpwJy+90R9V/KF5oWd8SQ8d1YWyHM0LKOxKW+p9SX34fkA0QvM5AY9GzZrFALlOqQ/lcGO2xx6JhXBbT0Zgn4044fWt2hIDy8q9VlBj5uiHiGV1+a87NG8quGgZE++yqOy8qyovE7vEtzRPTfRvR6VU4Nyw2iNTyyY1VKuyzQSE6oEFrRc2vQsU0Mo3qk5ZTNpxIqK5oK71CCz9biUvM08DV+WV8uajBDNf41Tqqc4JxPOr0ZILahSJrIt5KTOQiqpaqxw01aovSas6EDYpiv5PGGS80ILHtbl7BNYjQCxX+gb5OzcZAPIGDxNlBJighZUuBzyr9OuuIoGMa3SNOi42Fb0pBf+CnTTGPcOIp8zsAyNUAl84xecawLwXMC9/u+HaG+E72G6oIJsrfbeaze40zmcbKgEnk5del30yQeixVeTstuK6Sedq+oPiLIJb3pX2B8Qb1T6B8oUEbFyan7QLC35Q8OgjEYfRig4XuG6DkpV22q5Wrbeg6aAqGpTF22d4ZEXnkEsixmOKW3meIAeZ1cicLxr5F1TshgqfZ6GxKGaC1QTQSuiiBiGrMNdAii7kEUg6f9CpKFPundTpeWzYNN6lDjlYoFFQYrL7YS1Bg2qfCK4zYgLfrJKfy3457SR6fDbo+wwO8yO0quwypdaXm4vQeMUavSYmtIAP+i1Qcugs3NT8NheE9jKf9ivrctcUevxi9XHzJtCMFKcl3t4xrhUNEfSSp9hq9KYoku+SFk03hIsmMnBxsq7N2ZUzZsJODb0VkNR/n2PzD1a7Mma5Mkd2T08mf/0P+T74x/+x7vvn7376/7L+Zn4y/mv+fF//+dvB3/cjUHYSqeqGw2zxpIJVwl4gADXE64VaMcjBwr9jG3jJxjBlp0MW4G5567qzwiNnQhsfzIkTQWSTZVE4NPnLweu4bu0wroRJ3b0O2HFjpHAS/tLAjP+xxtxc3Tct+N0AnNdKHL8dM3cIuZH6yfx1ySnuHS8deSzVE0aRisw26xh3zm4IIrkauRGhtdNwv/NY+05/c/eJkEBRCeXOxEYo7yRilc+qciMAy2lIU/ErqtTeYCzKZ1BGV7FkWjYBuuUfKr0REF1VpfYNKWCLHBZypG+6UUjDV6UoaL9WsB6YBCX+OLurOA6lIRJLuQILcgkmjkYHqIzSi4lSg2q8XV6/s6u3ZrT3BaH9jRclivMaVZeMsNCxAdmy5FBpVmV9PsrXYEFs8eyvfxXoLJb6AC9s5btXxvSmCHRm49vIbuNMyAFd0XY0khxnw5LI74OEVRqLAjUuberh46Yb15dZLdoz/Hl2iz2ou6/YMdMTye9yb9k9twwFD299t5g8EzQTBF14U6AcbfORqtyUlo4Ol73tnqroLjcsi3Rg2Fms5FffWC2lgs1j7vr++1xdX7XqXRMhM2h04zS3WzOTtmOuKyJzPoOyWiwsVMOxHiExo4Z67/TQsJ/amlLp39ewl94WZqXDUvXf2vZctqv6YZ9yDx6yDx6yDx6yDx6yDx6yDx6yDx6yDx6yDx6yDzq4fEh8yiA8yHz6CHzCP58VZlHXMwws45T+6HT3fq/rB94Fw7rrmPCBM3nBn1gxxvqJ1fVmC31pWsQ4wcO9epOvFwW99ydk7KGErRYCMxmrhuNsv2QglY2mJnARwhlsw0zbbipnzdczG0jmrcZkBfuVMAKezD8HtXQQtxlMeV1OoIP2ArWp7m72gf6toFBu0DKJpC0CPTsAQlrwIaUlLAD3C813YP+39X+kwu585FYrflvssQbtP4e6B1t/x5A7+v5m8O/jo6fXk5Xy7/Lgvr6/aqV3Eq3Ty7iPtLMVur1m2zIoAKcBL2n1d8F8pX6/CZruEmXR12Xr/V5xWz9PHp4m/75g8zct+3OBr7ErJUEoPcYBOw4L1zU+g5i7n0bcFrsR9zJhguFKRXmznF9SLOaFmPEp4owJBVeSheD5rp1m0b8WuEOYppyXlNjdoDqnCWf4DLo3+hADoS6Te+KtSsErh+XcO5xFHN829LP9sX6ogKQAynB4pDN44JWI0iLzwSK080ErqxcL5CkFS1xOhxrcEF1Ern3kNjnVlNjqHLYK8HYlqWbbZJZeCuMYjFrqkTzQP3nHV5qBcnI1YaMa8EVyRWECFBFr0naRxmg9287Us53Rmhnr9T/r4Uj/V/X1u75zj/SiyefSd5Al6htoeB0Al1DiEkOsmfUMYh2+uSq9hsp9ieU7Q9SD3DHbe8eTDIQiKtXAr+PTA6aOSDKNSLC0q/VxP2+wsyEiIfdm2KfWFCKEWE0EXwhwTvr0vksQA6XCzJBNXQ3cu1GtWjOBnvKQCfFIrvLqWtT7Y+O1/Y8Qnups9cDUN2xKVF7bx8dHD7fO3i2d/T048HLk4NnJ0+Ps5fPnv73mtf3R9uuKiJT26poAPQFF1eUzS5NHFmy3fxtJJD9Oa/IPi7DHg03gm5hQR4WZ82NrvhI3LBW+1jc+BA9XFfcaLvnEdOp3JUrn+KcllRpsaGm1xwIGQvesEJLC5SYThFtj2Xk0obhN9ntL2OzGiQh0CG9wmyp1auctGE/H8NJ/Zim0yVEEhjFuhohyEX0AeDmUFErNciaM9AEbIpnKxaPLdqywL9/Co2HBVEk7Nvaht4QOQoSaCcENawgAlRbH2AlRjbQdhRG2Y5QXlLoTORe0iKQizAMo5kzdGaaD9ll4bKEEF3FW5BpPR4ZYQ6DdMUsXgAp2KbKnJ0jJeg1xWW5HCHGUYWVgsxOiLVQMAEW0DV06fMLwklOcDbJ8qwY37bqfCIIavAgrRsIdVr6nHWNFiAh7krYdhLYgzCcXgTmxS3iL+1HiTRaS2lQcTeIp885YzapAS4FEwEnyAyLwoQQSug4MwreNKk6E+qjWrUsbJLtci4KaToLfnx17lsmmQbNDjIDTk6o/rfFFGUUWjle/PW9jaR9LH3fDj1UO70Z3lQP9vmB3TlsOfty2V98J3ODSdcjH9iBDX1EOFeNM+GaDnlEVGjHj7RjeiRMbRSRm5l1gJWuhjj8bNUdZ29OpBu72sG5YWCyM3gIu23xexENjaEPvYG8DcakEKj6S8PyVocyx91+lxqmRSHjKhhM04nZoj1jsE82rX5lht93wMftRozKhwvNxyvMFM1d7oZz7X42zS9Gba9zrSBOm1K/cE31EulvJLA0M5QTAfpnm8TmWJXwo09xWUrfOjPHisy4WBpeZbPCpaJliQiDjt3w2kBegkbSlIKeguta8FpQ6Kt9S2ZkWfi2RE0Tkmb6Ipot8XeGKR3g+EU1obOGN7JcGtq1rSRpJ2xGel0NguDAsz5C2BXYBz7fQGl+rmklQ+ivLY5NFfp4PMVtvqHAizaBxdD8OLMPxqHzviubMH1ptLn9RWMChI3GM9aXkgZrnBkQx/r+0zcYFG2wDSiiIaGhrhYzhsz024+hDWNXo1dfmfu94wlBZ+fXx/rB2fn183aDB+DfIHl5A6WYC7US+i8fBL0SDEMM24DEslQzQWf2reTttFldL4/XA/HPkMgDXX7ahF0byWp0P3NNDBHQXTJqWmjXVPDObYbNOuD2QH0IX3oIX+qv6iF86SF8aV0kPoQvPYQvPYQv3TZ8yZYL6Zs42ofrB5C42iNdfVqFv3EBwUT63mx7y5mYJhx69soSIkSGApOmlBW2QJ7zS0IxIWPJcne8H89Nr7/oJCTcQ0vEe+sZFgQAuYKUDWPG4gMLGKpERwunYZkWYqXvMrs01Oi+N69X+IpIrUTVXEoaO4EQVMKLsRok6JodZEHBymHQfNcxZ5oUBEJ/BCUsB5+GlA2RxvKhxxSk0IuxLQ5B/48G1CKdjUNz3cZp4Vqk++xQVrS0YCwFlM2gyaptndiFtA23efqCPCOTKTnA5Hl+/O2Lo2JCvp0eHL44xofPn76YTF4eHb+YDpSeulPuZOvIICWWiubGNLtnV7WmFyMUhBzNt6l09kytyKYLeZ0fAPLrbEtD6GoMhmJf+6vkCwlcb8Gj4Ry6W4UPWvq5kyha4nbNPvXvtr1ZTJCGW7PId2aCF21fwLEjQtY2sYuGOC1N7UULriaNgkol6KTRw7hSToZeRAO2Ya++z7lUEql4ee0RMbZMZ9NzizZlUOzSBjzrtpIeFOHhU/Qm3PlwC2BZNinexXMYvaqRqpNCZ9yN33GB/kywkv1hqNRYK8gUN6WCWhy19xZ5PGoyHUfjWk/IFDGO3Di+P+M22ugNnIhN/HlBdumtTgMM4Hw2tvCB6U+buHoiJqnvN94hYweCHvUGbgkDdjLeY4hjYhl1ds7XEItmGEeI7B6TwCOrtpLw+8r2nYQJOvuyaVDaxjT0NDvK1m0a+F8u9C8mnVBSWYd+Wu4IZbn4lRZJsY2gJsq02Y4FljbqcIpwingG8ETqOamIwOUWKwK9cXP0xJRWvkCP6RRucvKZStWLNUSBvNJ2yQWXgkQ4F1xKJAh43W1VPU/WtBijgkN/4HQPg5f4ePrs4GDazugJGhwFHRk3fLaeiGs+WcdbZF4EdcTY4vajWrTdodb3DoV+Dusiup0U+wW9GtZL8+/s1ejeC1v0aPT1jS/gzTBljvpH9d/Dm5GC/nfwZqwCY4veDHO8/u28GQZs6x4IS2oNUNHX4NIYhrkH74Nf48Gv0V/Vg1/jwa+xLhIf/BoPfo0Hv8Ymfo1I52tEGSt8nz68Xa3effrw1t2wteDXtCCmTm1dEkX0rybBEclcq8EjG70LFXCxmt9SDxvuZnRficWmNw4p2hZDjYAqvS6IWs1jVS2hB7znysbcUZaoaDkKy7cVgMjK5LZg09FHIy8aEGKJMWhcOIdI+5LPLNXpz6m0uWC/NFK1QYquaGmL8I5mFvbk8THo/nM/PAbfxwJLD/TI73RXQhoyN8R4DvtvWCNblvOT4+On+8bY9qdf/xgZ375RvNbDD/ycppY7p82uUgunfq+Mjk4rrbpZHEK0ZiONqXpk2EyrAPtyANGI40aUmR5zPNIbDpHBKtoiQXLOpBIN2NG4QG6jDFnGJ75Hop0NudUWpPFsjvi2MH0Bo3ca/o18i4YdWMjOwDE8MWmTJ2PXdqrGgSoMIw9jZzPl9H5W+9qaaIZWG29XatlnzGRYadLTp9/xFxvmza2eYuvTQhMFEwNfLtuc9NiYau1GxlUCThjoBWJJO6riDjQ+474vmrXp9NUij+p4RQP6bNIqMpzkwBSZRX6eNY0jPXwfHz9NAn18/HRI81bzbdHGObQNG6IMe2y7JOEAg8yTbUGmDxlMYJmVF3oAVvOLyePuwh8N49fSYT0pModz/Sc41+Qz1JsOGiKEM0L4vDkGro1eNBDjehygZF8cNVgLfO5/wzDnpFH+rXgFqoMIY9dve6xVtWrhgiWYN2LfoRmh40iLPLloQtSC2I4JasHNaR+qtyDwrNpiC199ggL/DwhMU2VzSsbfjAMiVbwe3MxvkkzaAT+wtkYSsc1c7092/A7dDtrdpOyMfc8cwIw/DE2Il45ELzfMw9KbAvELXRdOus4NvGqkXugLT65xQHKKo1Z0zlw/V9+fEnxgoBmHlnP9hBKTANPeSDDRHEvTr0LNMTMegWLUaiIMSjEtnRQO/AHci4hPW5jma1bjUaK5qRiPCdmOHgUmz+h5r0RPooxP7IP7GkKufup4NZpuCJY37ev9GTgf9xPyg8sJieSBVdLjXF/vrvJCyWetcLUCTi2Gd21Wd0hRPgWA0RtodxfJjjdwnl1ptAxbcGeK8DWmZVsHoAc4qTDdnnasDx7M4OS9ASjmWG5NCLKhf44JzOPwu5A1mVABeBEqr3G2rKDrl34lcQl9kmTalBrLYyANKLEi7D8gUMoHE0HDDKB8XMbssNPlKsdMX2j2Gh9AV9c3cK/4+h7ibzyDpsYgAPdrFpoAol7FviQ8gCY16cUyE8mJlFgsB26euOBYe/+g8Plmt5AZ0t1FbTSEVnVsvRxXAsLdivrbpbGM+OHknC9sn+cFmfg4DAggCornm1oAWGjZq/GAR7WIvkLjlQX4Oo7HabGXVGV23vHfaFni/WfZAXpMz+eckf+JXp1/Qubv6KcLdHh0eWiaM7rSZ0/QaV2X5Gcy+ZGq/ecHz7LD7PAZevzjDx/fvR2Zd78n+RV/4sKD9g+PsgP0jk9oSfYPn705PH6JLvAUC7r//MDXvlrzyrgNFzaTrYfL0JPU7v8GbS/uZ0v/q7+TXUgif212kEaiaUaU3R8uDWlsjksLyEM7h4d2Dg/tHB7aOWy+sId2Dv+3t3P4Bn0kVc0FBiPbZ4gxJwq9yA5QgeV8wrEopCvwlLlPII2nkQrNuPfi5TJbVuDcgzosCyoJUkQqiQrOdhWMERcRnhCswlvUYAiX1Odi1VjNT+wdHYTzV3QmsMECGBP6o3a6ia0eufNycvRvfJtQrYHYek/ul59e/3SS6vNpza77JJf7Jtto//DFywjaJAQpUhnY+25rMyvPWMguyDXETPdF/gURBAlScR9w1VvQp7rQSuCUlkTjdJ9SuW8dpjjPORQDcpVN+upKVmPlI003WNC5/iwldIeiWmK6ijLfuG2D6d7pz24zHf7lVtPpz24xnZH0Np8vlBZ9bIQTGwfm4jKxuiCqcZOlpeW/gUl7O7jGpKnt609q6boRpT9q4IFf6wBcNILmWGFU8aIxFRAbCYb5LIx8DYI/7vE89z1Tkb/y0Z4e1jC9R17U/7P5V2KKV9ZnAz2QOYPvfCaAs4aBgae0RZxs+7pHsToeMVtFK/Jbq8D0mW2Xo4Ys2JiwO0OsZPAGjmgyPvmF5E6iN/+43ADpHitwEl3/VkCFS3SIICBCdCg11B0GJnmjP+poTVDQqyiorZimdShIvbApeTCPz7IY6hzayXO7TXINgGYywyxB4bp6FMQOQ9kvdHr+Lk1WzgFhiEpxdI0F5Y2ERrFtLbAUBdkSkVz0JYEVe3fuvgrF5f6QYdjxBmPahJloUMgAUbiqB/d9aH8a2dudTow4xEyeoMM19W8HieMR5pxShiqaCy5JzlkhkaQsJ+gTo58RqXk+76zHVj/blIpP2RJhMaEKJEY7SNOx27a1Aj14I5TjWkFTZJs1C3x/hHhtiL9cOn+Tf6N1KSBULBmuaB4bBXoaut8sPOsjPWISKGYUlzUWuJJ9u8IgK0m+MuG8JJitekXmuCTF5bTkWHUkeP0TZbPLKc4VFyfo8AD+hFThsBD50FaXnkTTEitU4bq2AQNNGOqpMWVDC01EgalImrApafm2h9MNbCZSYdXIfqxt8kTcoOtAwQcYz+jM9iBoEG1hR5kyi9nL9C6raEM7Vw1wA/inQWinKBOhnb3IzkG4ouXhReeXVVrnjVA6RAu8GKHGmcM/fXjbk01INvPqj1Vmtf5zQ/hbLyA34N9QwXMbi3Fjp9aQ2fC3k51B0HqmpXsCS487cgUxloY37mEpSTUpvwzSE/2372118yGh1iA9gH4Y852gtXuDrU4FrAFcEK02BA1W821hq04FphlMtVFpA3AFAbz3DJWtC7F+LJoFuR98NkSAsZv73gAHt3YMmwmoN/CZGKoEp9dn6zJlprylLf+ju6A62qqDqSTYiEzcRISGQmgMWUXUnN+Lf8HDZIbcFKT2XjVX7tckHsCyEjKCg3WEyCxDu0cHB7tpaztlVM6TNtyUlHfDfW8/QZQVNDe2iTYqvQUKjBJuZp8AEMntnaJZ68rtF1F5rJtUyg5ru01Ef1WZFhsutIklavuQipr2EiF1BdQCCkVX5irxhJSXilR1iRU5QTv//CfIrP/6V2gs5zVhlyVlV5eUXdoeFZcKT3qicyOCwTqCe0WZ4wAnaOdZdpAd7HS2FqY+QTtZto/rev+KTjDD3+x72/n+8eHkWfHt0cHei5dHh3uHh+TF3sv8+MXe82eTl8fPJs/y6eTpny7xHx8DLz0x/7k0LPXkMWa4XP5GLhe0LHIsipP/R43Mi7tW98rC8jon/3F05NHxH0dHu0+ePNlZuajnelF7uKzn+HDraysxmzV4Rk7KJieMrFzJ39t9/fvObrwMfk3EQlC9+615CK30Lt09XGBNor0RNsKuqeCsSrmgN64lDAC1AyZ4s8P5XXhzT9K5h4xi28YJYoQcjDasKwHBPd7DnW1dBw5/zzVM0QRn/J1RacH6PbA3MHUQK1+RBRdXXxvKPGC/B9KSk3vTVdt+YviOX2UM666D3kM2SgCUb6ZkZ13vjv4vShboosZMbveShjt602vsKXlakBcHB3svCnJgrrHJ4eGzvWL6bf7tQYGPiunhra7osJUILTa+nMO7edtrGriaOyvw4EcXshdNQY3vk1tfXl5Fbu2MtiAiFgTt2rF3oTyTy0EAL1Xotmrb1Q2Mw7hyYJqR9APImZCaOE0QjpFGesu7u5/lR/Oy5gO+YIrN7ciJC6SwcsaudImCj0FRsTrZ7gjtTnB+NYM+Vr/wye4IEZU/edTjIjlBXwcDAQ5y9tpxPwOa4kEFXuPAmBCt5UmkeNefY3opfpWrsbClPEbrtYnegu71vhsPEoTLmz+/iwC9ASihUXJTTfcn61AyhvS0vttbLzRoUyRXzV0zezR5hKN534SGpiV2E32Qc1FAndaNNidhqr0loF3D7D0CSfvOog2KxMXwYOX6iAVZb5sA828RD71aMA5nXBmGvNpoeoPJFOzPLsZ4Y6IY3IuOZ37TI30W4LI2Hb28WbIt5Oz6Zppegel6qTdcDl+4K2/0+1068t54AOrEtbd+xcP3DRT28hhurz9nFOn18l4HpvsBqgfMru++fBuour1972CYqfudfFcA4XlVnMuwfpxJSaUi+iTcDfpTW3xCRc0T/eDIlxxMJUffJOYkhZw0kIMguhy28OIq+Wxm6jeFQSOD250QBjcE4qzXLOGWIMQ5phtC8QbSR28DgA8DCjOt196+4CbvXe3DKDuPAl5bNm3jl9prZRTU5cKQWWiTEbOIDEw8Np/o+yCsV/GXve9cb1r9NzQnuCBipGdvdY4pFVLZLHnbgLQF0JerCludBdOCFozmfEH08ajobK6QzUx1yZy0XGp0S6WXY2o+2RIUUy5mJsgJowqXNIdIuQ32bO2c0P8/JIF2MbGSLntJoRuep02SPr/SlM8VLAeZegOXg4GkbTxgi7vOw5XVgKdqAdX/YGNtv1qqaX6Oy6mpJKgxaVyv0cFG+xopOa8mUSnegeXYGNW85E3RRqm+0v90QYTQvQBrATAdsfrO/mpIM48+lQgXQRgiLopLeOHSDRlEmD7q0GLLV3lTZLXgv5BctW3vPdHYX/Y+rz6qHY+J/kRj9HvOZyUxK/bR7aclxbZDTlmEUdJB3ADOPGCw1OjY9fNjki+vDM4O5nDdSNquAKun8R1y/Psbz7RGyk9nrpvyfhKz2SYxl4E9cPVk9oO1M5iCuaxxk5ZULS9XxtiHEw59te6sltLW3bgela87jynButYc0avd8S0/KHh+BVRqGcJr9+/E4TK/QYBIt52G/U0fbTnnQl2ahIDWvIhZPufCzbfnmcFAhLsHC20kzltDApTO6HHlEE0BqtKfJLdjYKoq5Sq+cTb9VTcffoNZk8asmya9/XTgnZJtns8PfKG17QpDZQ9J/tSDZVXo+MaBMXhGjINM+mwLa+e0dPuD+VdikDM25SG1ctFaaByXCQhUP0+RJ/rnv9zMV82ECEZMRX47/4/hswQU7e/+ko1vzHZQFM6++jS1H914oiKgNztVNS/S5LaZIydYIC+MNpucqrmruyGY6ZwX6NPZ67THQNY4v79FtSP2J+NF76jfcTKX9Z4KXtHH5ObjuN5E9txXuE44ABjjCvjHvU0XDJme8wYGeFt8+mEHkHoTt7/7vGbcNpNszyXMWQZzev7O5Jx1+Qs83PO+USOgc2ETz1Ks4JqShc2TezS8CDPFCdopeP633TDObPcfmaS/kcdP0P9CB+hPKPWC0fZPzG+pYLXkIAMvusFYU5ZOY/POd1RjysrWQt6PrkhHVgDefGjFWmEV6ZCKG8Ipbg47eDF9PsX5wd6L/Dk2YQcYP3u293RyUDw7yl8c5s8PvlC043rBFPe+orvFOEa0DWSNaIFo3pGSV1E3fJXBXUjZ7PKKLFvy/OMfgT417Tl6Tr4OQN0Hde6lljR4aQ/wmdfQHCBIH8TM2hh8dXVIpLVtZhDPNdFb4wN2teLbRpu3yylc6ya3mZxrNieKQwnMTqyye3RBypuyFvRGm40FatCd+Z1r85bjsrQm0gWW1uhAKyyWqCaiJkpgxdu2e0PpACEx3e12+d6O9CNZdqzc5mzou6GREDrjJr1tcJrJgX5t+ul9hUFqz6YHL/HLF/fPWfsM4IsGqm26rgH+mlhFHKzWo1LyOSe1SgW5dF3wq7S5tJffmqFLrQ4JvmD9s51M5WwltKKbWba2T/+j4yQm60UShRZzW9raPJ/juiaMFDYDSgtZEyzDr7I0WBWRMhYY0RDLQWm2k4TWO1sMAHaWIRigBsddkGOreHgVp+fzsXuVnL4TA3jT5P46SA42x6woSfeb4Ryj9VB6ZlKMuIgyjAxuIRYCN7O5gq4GxgXn2jlDkpRLOeqrYrzvV97goJzGVS78mXFmA83WfcGAjQ4LBGnchSAkuSbQv8hniOVcdBtvBmiYEdHvwLXhnD3ftfDNu/sR6Vs/gW0JknIZX7CrDyMUTbhcBdStgpBOzcBECSgw7GBAbzS72jVdpBhXWuFkJIcWMf8hd7uBSD7fuyZCLd0o0Dhc0bIElyYV4J1lRdBPhvza4NLFFkUrNE2BIVm/LnFO5rwEb5Eg8M+iD4HrHiKparALRe6MqiGK23sbdRYpPoPz662E0sWpWTV6R+vRJnjNNdvcSVvs7Es2HdL29bZOxlfnn2z38IqLJWrMSn3EdMc2PhTEFtv2rGSUUthlGGy3tgowNp/5mtHS9vuype7sigbCPP21WjePYvKMGdggWY7zuulNrfEG7oN22wcdq1zhMmNcVFmd98X1gZIgTnqticjjZLEbVAr7gaYtPgU4IUdH1hBlvXTORIhNMlVigA1hqfqhlVHAA3VRAnAu7UimRZubKeeCmG70VCEBvbWj0YCIDjStHx4c/EfW3SJDhLfcJfNxb6MsYW+yV70t6oSlua3ptrJdtTF6XAtLP7cA56pJTLvJBQsjtKeYFLALU0FI6iptn7S5WmStzOuhtd+UA23g07M4ICkzg2ToDMJ/clzmtv+4FkgLxI1k8tNFhn5i6C1lzWeIo+FMQiMYn0/lx+xMWpeNHjafW5qcNNMpERKG++niL6bvNcJINhDtGgKnX9eDU4ZzCL20W6c//dlUNh3Z7+HG6F5/3PGszH6oBx/3CD6OiN2U4u3XAcm7c+0cMyM4lZ7jB4y+wzNXOCIDtnkLwlyXea6izUEGegMLXcVE16ghcL+M9L5ZaZ+ZdtHWOxNrbN47+Ka1wetdolC/Q6MjFVJbCzKln0/Qzt8A/f/YWWtLJf1tm+wGshaB5V5TEXLGcM/mOFqIT/OVMutPd//wfSAS2l6gC6LQBf2NmCZWuNJiu6aCBMg8z5uamjBPyA6z7zz+cPruSVi9b880valwHftdLoLHEYT+B6j4KhFhguZzyJNT8zCaythwzJ6aSS5N0LrfZ9dxp8J1FoKRlAeD39GQUXZ9/25cHiP97Q2bky6VkZoUrcpSvnMeBktUzAC7q0dZlDKXAiqVw3wnuBxufCH23gZMGlaUQBGk073vFvbft66bvEODXzl0qNHXslXU9HwuShUs/yo+DDXYUsJzYJ7ER6DGLO19HKpeCe5HSB11dLmmx87mQsdHoca3zPnu7cKcfL4cdlig9XWJILuixsw1W8VSz4AIy3lBin6R9A1zGTerf/m9KY2uX6Ez5ukDGwhdFm3Oa2IehznMiXyP9bJ6NwPx1om+J2i3mGQ1l2omiPy1zMC8vTtCu46AMiIm+t8g1drk38SyZDPZyspO0bQRYFWUzWSvoNc0jJPWc6DHYFZu1zBCUQWwJ4ngizxph78rrGDcc+i/ogz2wyaxUTV329BM9gDusNOzFciAosx6THtKZ3NKLEIqLNYvLRnC2cb1Jxl1T+RI4uEGXOg/P02nkqge6wzOx65s69y6ZnRLZ5aFBYbcYNStOZugw6IR3Z59axcA+XKYeW2h3HR1csly1FvaRvUOrLmeyMhcD4S3wBKRzyRvNEXqqeaCM97IEvo64uhJWwpcX3dxEZHg1vsY/RAbgtufNorAufeCJclLo2PdTicyhKOkxZvb+KCHrpuwKEr/1pGUzcpWcDNFHL5/8xHtN5IIuX9Ci90U4177wIQg3/85ukk+BdWqiI4NlSFK1jk+gsimvGMVrI+mfF5TqqDAhL/v0Q8fP55HZQA1MeuHe6bHYRG+noKxwuJqs1rSyQXcpnBzVKg5KOBs2giCgqiBM4guSzqMaHgv+0P2hw0XMmBW2bRQdYJx1phdAq++041ZCF7XAx7Ufk3m9FhohfE3HjFxTFDKnWbsDq0ab6G0JWZM0HZ8WLJH/18AAAD//9t+xkI=" } diff --git a/ingest/pipeline/definition.json b/ingest/pipeline/definition.json index 87963fddfb7..48940d209aa 100644 --- a/ingest/pipeline/definition.json +++ b/ingest/pipeline/definition.json @@ -12,8 +12,8 @@ }, { "user_agent" : { - "field": "context.user.user-agent", - "target_field": "context.user.user_agent", + "field": "user_agent.original", + "target_field": "user_agent_parsed", "ignore_missing": true } } diff --git a/model/error/event.go b/model/error/event.go index a5c484a0601..01843fa28ad 100644 --- a/model/error/event.go +++ b/model/error/event.go @@ -32,6 +32,7 @@ import ( m "github.com/elastic/apm-server/model" "github.com/elastic/apm-server/model/error/generated/schema" + "github.com/elastic/apm-server/model/metadata" "github.com/elastic/apm-server/transform" "github.com/elastic/apm-server/utility" "github.com/elastic/apm-server/validation" @@ -75,6 +76,8 @@ type Event struct { TransactionSampled *bool TransactionType *string + User *metadata.User + data common.MapStr } @@ -163,6 +166,14 @@ func DecodeEvent(input interface{}, err error) (transform.Transformable, error) return nil, err } + if ok, _ := e.Context.HasKey("user"); ok { + user, err := e.Context.GetValue("user") + e.User, err = metadata.DecodeUser(user, err) + if err != nil { + return nil, err + } + } + return &e, nil } @@ -180,8 +191,12 @@ func (e *Event) Transform(tctx *transform.Context) []beat.Event { "error": e.fields(tctx), "processor": processorEntry, } - tctx.Metadata.Merge(fields) + delete(e.Context, "user") utility.Add(fields, "context", e.Context) + utility.Add(fields, "user", e.User.Fields()) + utility.Add(fields, "client", e.User.ClientFields()) + utility.Add(fields, "user_agent", e.User.UserAgentFields()) + tctx.Metadata.Merge(fields) // sampled and type is nil if an error happens outside a transaction or an (old) agent is not sending sampled info // agents must send semantically correct data diff --git a/model/error/event_test.go b/model/error/event_test.go index e3c8269e99c..da9af82e920 100644 --- a/model/error/event_test.go +++ b/model/error/event_test.go @@ -81,7 +81,9 @@ func TestErrorEventDecode(t *testing.T) { id, culprit := "123", "foo()" parentId, traceId, transactionId := "0123456789abcdef", "01234567890123456789abcdefabcdef", "abcdefabcdef0000" - context := map[string]interface{}{"a": "b"} + name, userId, email, userIp := "jane", "abc123", "j@d.com", "127.0.0.1" + context := map[string]interface{}{"a": "b", "user": map[string]interface{}{ + "username": name, "email": email, "ip": userIp, "id": userId}} code, module, attrs, exType, handled := "200", "a", "attr", "errorEx", false exMsg, paramMsg, level, logger := "Exception Msg", "log pm", "error", "mylogger" transactionSampled := true @@ -114,6 +116,7 @@ func TestErrorEventDecode(t *testing.T) { Culprit: &culprit, Context: context, Timestamp: timestampParsed, + User: &metadata.User{Id: &userId, Name: &name, IP: &userIp, Email: &email}, }, }, { @@ -224,7 +227,6 @@ func TestErrorEventDecode(t *testing.T) { } { transformable, err := DecodeEvent(test.input, test.inpErr) - fmt.Println(err) if test.e != nil { event := transformable.(*Event) assert.Equal(t, test.e, event, fmt.Sprintf("Failed at idx %v", idx)) @@ -407,6 +409,9 @@ func TestEvents(t *testing.T) { sampledTrue, sampledFalse := true, false transactionType := "request" + email, userIp, userAgent := "m@m.com", "127.0.0.1", "js-1.0" + uid := "1234567889" + tests := []struct { Transformable transform.Transformable Output common.MapStr @@ -415,15 +420,12 @@ func TestEvents(t *testing.T) { { Transformable: &Event{Timestamp: timestamp}, Output: common.MapStr{ - "context": common.MapStr{ - "service": common.MapStr{ - "agent": common.MapStr{"name": "", "version": ""}, - "name": "myservice", - }, - }, + "agent": common.MapStr{"name": "", "version": ""}, + "service": common.MapStr{"name": "myservice"}, "error": common.MapStr{ "grouping_key": "d41d8cd98f00b204e9800998ecf8427e", }, + "user": common.MapStr{"id": uid}, "processor": common.MapStr{"event": "error", "name": "error"}, "timestamp": common.MapStr{"us": timestampUs}, }, @@ -433,15 +435,12 @@ func TestEvents(t *testing.T) { Transformable: &Event{Timestamp: timestamp, TransactionSampled: &sampledFalse}, Output: common.MapStr{ "transaction": common.MapStr{"sampled": false}, - "context": common.MapStr{ - "service": common.MapStr{ - "agent": common.MapStr{"name": "", "version": ""}, - "name": "myservice", - }, - }, + "agent": common.MapStr{"name": "", "version": ""}, + "service": common.MapStr{"name": "myservice"}, "error": common.MapStr{ "grouping_key": "d41d8cd98f00b204e9800998ecf8427e", }, + "user": common.MapStr{"id": uid}, "processor": common.MapStr{"event": "error", "name": "error"}, "timestamp": common.MapStr{"us": timestampUs}, }, @@ -455,6 +454,9 @@ func TestEvents(t *testing.T) { "grouping_key": "d41d8cd98f00b204e9800998ecf8427e", }, "processor": common.MapStr{"event": "error", "name": "error"}, + "service": common.MapStr{"name": "myservice"}, + "user": common.MapStr{"id": uid}, + "agent": common.MapStr{"name": "", "version": ""}, "timestamp": common.MapStr{"us": timestampUs}, }, Msg: "Payload with valid Event.", @@ -462,7 +464,7 @@ func TestEvents(t *testing.T) { { Transformable: &Event{ Timestamp: timestamp, - Context: common.MapStr{"foo": "bar", "user": common.MapStr{"email": "m@m.com"}}, + Context: common.MapStr{"foo": "bar", "user": common.MapStr{"email": "test@m.com"}}, Log: baseLog(), Exception: &Exception{ Message: &exMsg, @@ -470,16 +472,18 @@ func TestEvents(t *testing.T) { }, TransactionId: &trId, TransactionSampled: &sampledTrue, + User: &metadata.User{Email: &email, IP: &userIp, UserAgent: &userAgent}, }, Output: common.MapStr{ "context": common.MapStr{ - "foo": "bar", "user": common.MapStr{"email": "m@m.com"}, - "service": common.MapStr{ - "name": "myservice", - "agent": common.MapStr{"name": "", "version": ""}, - }, + "foo": "bar", }, + "service": common.MapStr{"name": "myservice"}, + "agent": common.MapStr{"name": "", "version": ""}, + "user": common.MapStr{"email": email}, + "client": common.MapStr{"ip": userIp}, + "user_agent": common.MapStr{"original": userAgent}, "error": common.MapStr{ "grouping_key": "1d1e44ffdf01cad5117a72fd42e4fdf4", "log": common.MapStr{"message": "error log message"}, @@ -504,9 +508,7 @@ func TestEvents(t *testing.T) { }, } - me := metadata.NewMetadata( - &service, nil, nil, nil, - ) + me := metadata.NewMetadata(&service, nil, nil, &metadata.User{Id: &uid}) tctx := &transform.Context{ Metadata: *me, Config: transform.Config{SmapMapper: &sourcemap.SmapMapper{}}, @@ -517,6 +519,7 @@ func TestEvents(t *testing.T) { outputEvents := test.Transformable.Transform(tctx) require.Len(t, outputEvents, 1) outputEvent := outputEvents[0] + assert.Equal(t, test.Output, outputEvent.Fields, fmt.Sprintf("Failed at idx %v; %s", idx, test.Msg)) assert.Equal(t, test.Output["timestamp"], outputEvent.Fields["timestamp"], fmt.Sprintf("Failed at idx %v; %s", idx, test.Msg)) assert.Equal(t, test.Output["transaction"], outputEvent.Fields["transaction"], fmt.Sprintf("Failed at idx %v; %s", idx, test.Msg)) assert.Equal(t, test.Output["exception"], outputEvent.Fields["exception"], fmt.Sprintf("Failed at idx %v; %s", idx, test.Msg)) diff --git a/model/metadata/metadata.go b/model/metadata/metadata.go index be87ee75fac..8a0bbbcc02d 100644 --- a/model/metadata/metadata.go +++ b/model/metadata/metadata.go @@ -80,7 +80,10 @@ func (m *Metadata) Merge(fields common.MapStr) common.MapStr { utility.Add(fields, "host", m.System.fields()) utility.Add(fields, "process", m.Process.fields()) utility.MergeAdd(fields, "service", m.Service.fields()) - utility.MergeAdd(fields, "user", m.User.fields()) + utility.AddIfNil(fields, "user", m.User.Fields()) + utility.AddIfNil(fields, "client", m.User.ClientFields()) + utility.AddIfNil(fields, "user_agent", m.User.UserAgentFields()) + return fields } diff --git a/model/metadata/metadata_test.go b/model/metadata/metadata_test.go index 8260ec560f7..8fc5fbafb13 100644 --- a/model/metadata/metadata_test.go +++ b/model/metadata/metadata_test.go @@ -170,7 +170,7 @@ func TestMetadataMerge(t *testing.T) { "name": "myservice", }, "process": common.MapStr{"pid": pid}, - "user": common.MapStr{"id": "12321", "email": "override@email.com"}, + "user": common.MapStr{"email": "override@email.com"}, }, }, } { diff --git a/model/metadata/user.go b/model/metadata/user.go index 79ccf1181a6..5bb57e97679 100644 --- a/model/metadata/user.go +++ b/model/metadata/user.go @@ -18,6 +18,7 @@ package metadata import ( + "encoding/json" "errors" "github.com/elastic/apm-server/utility" @@ -27,7 +28,7 @@ import ( type User struct { Id *string Email *string - Username *string + Name *string IP *string UserAgent *string } @@ -42,24 +43,48 @@ func DecodeUser(input interface{}, err error) (*User, error) { } decoder := utility.ManualDecoder{} user := User{ - Id: decoder.StringPtr(raw, "id"), - Email: decoder.StringPtr(raw, "email"), - Username: decoder.StringPtr(raw, "username"), - IP: decoder.StringPtr(raw, "ip"), UserAgent: decoder.StringPtr(raw, "user-agent"), + IP: decoder.StringPtr(raw, "ip"), + Name: decoder.StringPtr(raw, "username"), + Email: decoder.StringPtr(raw, "email"), + } + + //id can be string or int + tmp := decoder.Interface(raw, "id") + if tmp != nil { + if t, ok := tmp.(json.Number); ok { + id := t.String() + user.Id = &id + } else if t, ok := tmp.(string); ok && t != "" { + user.Id = &t + } } return &user, decoder.Err } -func (u *User) fields() common.MapStr { +func (u *User) Fields() common.MapStr { if u == nil { return nil } user := common.MapStr{} utility.Add(user, "id", u.Id) utility.Add(user, "email", u.Email) - utility.Add(user, "username", u.Username) + utility.Add(user, "name", u.Name) + return user +} + +func (u *User) ClientFields() common.MapStr { + if u == nil { + return nil + } + user := common.MapStr{} utility.Add(user, "ip", u.IP) - utility.Add(user, "user-agent", u.UserAgent) return user } + +func (u *User) UserAgentFields() common.MapStr { + if u == nil || u.UserAgent == nil { + return nil + } + return common.MapStr{"original": *u.UserAgent} +} diff --git a/model/metadata/user_test.go b/model/metadata/user_test.go index 412483eed9b..9c55dc0378c 100644 --- a/model/metadata/user_test.go +++ b/model/metadata/user_test.go @@ -18,6 +18,7 @@ package metadata import ( + "encoding/json" "errors" "testing" @@ -26,11 +27,11 @@ import ( "github.com/elastic/beats/libbeat/common" ) -func TestUserTransform(t *testing.T) { +func TestUserFields(t *testing.T) { id := "1234" ip := "127.0.0.1" email := "test@mail.co" - username := "user123" + name := "user123" userAgent := "rum-1.0" tests := []struct { @@ -46,21 +47,87 @@ func TestUserTransform(t *testing.T) { Id: &id, IP: &ip, Email: &email, - Username: &username, + Name: &name, UserAgent: &userAgent, }, Output: common.MapStr{ - "ip": "127.0.0.1", - "id": "1234", - "email": "test@mail.co", - "username": "user123", - "user-agent": "rum-1.0", + "id": "1234", + "email": "test@mail.co", + "name": "user123", }, }, } for _, test := range tests { - output := test.User.fields() + output := test.User.Fields() + assert.Equal(t, test.Output, output) + } +} + +func TestUserClientFields(t *testing.T) { + id := "1234" + ip := "127.0.0.1" + email := "test@mail.co" + name := "user123" + userAgent := "rum-1.0" + + tests := []struct { + User User + Output common.MapStr + }{ + { + User: User{}, + Output: common.MapStr{}, + }, + { + User: User{ + Id: &id, + IP: &ip, + Email: &email, + Name: &name, + UserAgent: &userAgent, + }, + Output: common.MapStr{ + "ip": "127.0.0.1", + }, + }, + } + + for _, test := range tests { + output := test.User.ClientFields() + assert.Equal(t, test.Output, output) + } +} + +func TestUserAgentFields(t *testing.T) { + id := "1234" + ip := "127.0.0.1" + email := "test@mail.co" + name := "user123" + userAgent := "rum-1.0" + + tests := []struct { + User User + Output common.MapStr + }{ + { + User: User{}, + Output: nil, + }, + { + User: User{ + Id: &id, + IP: &ip, + Email: &email, + Name: &name, + UserAgent: &userAgent, + }, + Output: common.MapStr{"original": "rum-1.0"}, + }, + } + + for _, test := range tests { + output := test.User.UserAgentFields() assert.Equal(t, test.Output, output) } } @@ -77,19 +144,14 @@ func TestUserDecode(t *testing.T) { {input: nil, inputErr: nil, err: nil, u: nil}, {input: nil, inputErr: inpErr, err: inpErr, u: nil}, {input: "", err: errors.New("Invalid type for user"), u: nil}, - { - input: map[string]interface{}{"id": 1}, - err: errors.New("Error fetching field"), - u: &User{Id: nil, Email: nil, Username: nil, IP: nil, UserAgent: nil}, - }, + {input: map[string]interface{}{"id": json.Number("12")}, inputErr: nil, err: nil, u: &User{Id: &id}}, { input: map[string]interface{}{ - "id": id, "email": mail, "username": name, - "ip": ip, "user-agent": agent, + "id": id, "email": mail, "username": name, "ip": ip, "user-agent": agent, }, err: nil, u: &User{ - Id: &id, Email: &mail, Username: &name, IP: &ip, UserAgent: &agent, + Id: &id, Email: &mail, Name: &name, IP: &ip, UserAgent: &agent, }, }, } { diff --git a/model/transaction/event.go b/model/transaction/event.go index afb6037f296..bb4e078ba7e 100644 --- a/model/transaction/event.go +++ b/model/transaction/event.go @@ -23,6 +23,7 @@ import ( "github.com/santhosh-tekuri/jsonschema" + "github.com/elastic/apm-server/model/metadata" "github.com/elastic/apm-server/model/transaction/generated/schema" "github.com/elastic/apm-server/transform" "github.com/elastic/apm-server/utility" @@ -62,6 +63,7 @@ type Event struct { Marks common.MapStr Sampled *bool SpanCount SpanCount + User *metadata.User ParentId *string TraceId string @@ -106,6 +108,14 @@ func DecodeEvent(input interface{}, err error) (transform.Transformable, error) return nil, decoder.Err } + if ok, _ := e.Context.HasKey("user"); ok { + user, err := e.Context.GetValue("user") + e.User, err = metadata.DecodeUser(user, err) + if err != nil { + return nil, err + } + } + return &e, nil } @@ -134,6 +144,7 @@ func (t *Event) fields(tctx *transform.Context) common.MapStr { } utility.Add(tx, "span_count", spanCount) } + return tx } @@ -149,10 +160,14 @@ func (e *Event) Transform(tctx *transform.Context) []beat.Event { "processor": processorEntry, transactionDocType: e.fields(tctx), } + delete(e.Context, "user") utility.Add(fields, "context", e.Context) utility.AddId(fields, "parent", e.ParentId) utility.AddId(fields, "trace", &e.TraceId) utility.Add(fields, "timestamp", utility.TimeAsMicros(e.Timestamp)) + utility.Add(fields, "user", e.User.Fields()) + utility.Add(fields, "client", e.User.ClientFields()) + utility.Add(fields, "user_agent", e.User.UserAgentFields()) tctx.Metadata.Merge(fields) events = append(events, beat.Event{Fields: fields, Timestamp: e.Timestamp}) diff --git a/model/transaction/event_test.go b/model/transaction/event_test.go index 796e55b04db..93a94c22833 100644 --- a/model/transaction/event_test.go +++ b/model/transaction/event_test.go @@ -61,7 +61,9 @@ func TestTransactionEventDecode(t *testing.T) { timestampEpoch := json.Number(fmt.Sprintf("%d", timestampParsed.UnixNano()/1000)) traceId, parentId := "0147258369012345abcdef0123456789", "abcdef0123456789" dropped, started, duration := 12, 6, 1.67 - context := map[string]interface{}{"a": "b"} + name, userId, email, userIp := "jane", "abc123", "j@d.com", "127.0.0.1" + context := map[string]interface{}{"a": "b", "user": map[string]interface{}{ + "username": name, "email": email, "ip": userIp, "id": userId}} marks := map[string]interface{}{"k": "b"} sampled := true @@ -70,24 +72,6 @@ func TestTransactionEventDecode(t *testing.T) { err error e *Event }{ - // traceId missing - { - input: map[string]interface{}{ - "id": id, "type": trType, "duration": duration, "timestamp": timestampEpoch, - "span_count": map[string]interface{}{"started": 6.0}}, - err: errors.New("Error fetching field"), - }, - // minimal event - { - input: map[string]interface{}{ - "id": id, "type": trType, "duration": duration, "timestamp": timestampEpoch, - "trace_id": traceId, "span_count": map[string]interface{}{"started": 6.0}}, - e: &Event{ - Id: id, Type: trType, TraceId: traceId, - Duration: duration, Timestamp: timestampParsed, - SpanCount: SpanCount{Started: &started}, - }, - }, // full event, ignoring spans { input: map[string]interface{}{ @@ -105,6 +89,7 @@ func TestTransactionEventDecode(t *testing.T) { Duration: duration, Timestamp: timestampParsed, Context: context, Marks: marks, Sampled: &sampled, SpanCount: SpanCount{Dropped: &dropped, Started: &started}, + User: &metadata.User{Id: &userId, Name: &name, IP: &userIp, Email: &email}, }, }, } { @@ -194,7 +179,7 @@ func TestEventTransform(t *testing.T) { Result: &result, Timestamp: time.Now(), Duration: 65.98, - Context: common.MapStr{"foo": "bar"}, + Context: map[string]interface{}{"foo": "bar"}, Sampled: &sampled, SpanCount: SpanCount{Started: &startedSpans, Dropped: &dropped}, }, @@ -225,6 +210,8 @@ func TestEventsTransformWithMetadata(t *testing.T) { platform := "x64" timestamp, _ := time.Parse(time.RFC3339, "2019-01-03T15:17:04.908596+01:00") timestampUs := timestamp.UnixNano() / 1000 + id, name, ip, userAgent := "123", "jane", "63.23.123.4", "node-js-2.3" + user := metadata.User{Id: &id, Name: &name, IP: &ip, UserAgent: &userAgent} service := metadata.Service{Name: "myservice"} system := &metadata.System{ @@ -276,13 +263,15 @@ func TestEventsTransformWithMetadata(t *testing.T) { "sampled": true, }, } - txWithContext := Event{Timestamp: timestamp, Context: common.MapStr{"foo": "bar", "user": common.MapStr{"id": "55"}}} + txWithContext := Event{Timestamp: timestamp, Context: common.MapStr{"foo": "bar"}, User: &user} txWithContextEs := common.MapStr{ "agent": common.MapStr{"name": "", "version": ""}, "context": common.MapStr{ - "foo": "bar", - "user": common.MapStr{"id": "55"}, + "foo": "bar", }, + "user": common.MapStr{"id": "123", "name": "jane"}, + "client": common.MapStr{"ip": "63.23.123.4"}, + "user_agent": common.MapStr{"original": userAgent}, "host": common.MapStr{ "architecture": "darwin", "hostname": "a.b.c", diff --git a/processor/stream/package_tests/error_attrs_test.go b/processor/stream/package_tests/error_attrs_test.go index 75e2a4c3407..a74a197d4e8 100644 --- a/processor/stream/package_tests/error_attrs_test.go +++ b/processor/stream/package_tests/error_attrs_test.go @@ -44,16 +44,18 @@ func errorPayloadAttrsNotInFields() *tests.Set { tests.Group("error.exception.attributes"), "error.exception.stacktrace", "error.log.stacktrace", + tests.Group("context.user"), ) } func errorFieldsNotInPayloadAttrs() *tests.Set { return tests.NewSet( "view errors", "error id icon", - "context.user.user-agent", "context.user.ip", "context.http", "context.http.status_code", "host.ip", tests.Group("observer"), + tests.Group("user"), + tests.Group("client"), ) } @@ -120,6 +122,7 @@ func errorKeywordExceptionKeys() *tests.Set { tests.Group("observer"), tests.Group("process"), tests.Group("service"), + tests.Group("user"), ) } @@ -131,7 +134,8 @@ func TestErrorPayloadAttrsMatchFields(t *testing.T) { func TestErrorPayloadAttrsMatchJsonSchema(t *testing.T) { errorProcSetup().PayloadAttrsMatchJsonSchema(t, - errorPayloadAttrsNotInJsonSchema(), nil) + errorPayloadAttrsNotInJsonSchema(), + tests.NewSet("error.context.user.email")) } func TestErrorAttrsPresenceInError(t *testing.T) { diff --git a/processor/stream/package_tests/metadata_attrs_test.go b/processor/stream/package_tests/metadata_attrs_test.go index a8c56df5bd6..486d94f4de2 100644 --- a/processor/stream/package_tests/metadata_attrs_test.go +++ b/processor/stream/package_tests/metadata_attrs_test.go @@ -104,8 +104,7 @@ func TestMetadataPayloadAttrsMatchFields(t *testing.T) { {Template: "system.platform", Mapping: "host.os.platform"}, {Template: "system", Mapping: "host"}, {Template: "service.agent", Mapping: "agent"}, - {Template: "user.username", Mapping: "context.user.username"}, - {Template: "user", Mapping: "context.user"}, + {Template: "user.username", Mapping: "user.name"}, {Template: "process.argv", Mapping: "process.args"}, } setup.EventFieldsMappedToTemplateFields(t, eventFields, mappingFields) @@ -129,12 +128,13 @@ func TestKeywordLimitationOnMetadataAttrs(t *testing.T) { tests.Group("transaction"), tests.Group("parent"), tests.Group("trace"), + "user_agent.original", ), []tests.FieldTemplateMapping{ {Template: "agent", Mapping: "service.agent"}, {Template: "host.os.platform", Mapping: "system.platform"}, {Template: "host", Mapping: "system"}, - {Template: "context.user", Mapping: "user"}, + {Template: "user.name", Mapping: "user.username"}, }, ) } diff --git a/processor/stream/package_tests/span_attrs_test.go b/processor/stream/package_tests/span_attrs_test.go index fbbc106f186..60c1b667a1c 100644 --- a/processor/stream/package_tests/span_attrs_test.go +++ b/processor/stream/package_tests/span_attrs_test.go @@ -46,6 +46,7 @@ func spanPayloadAttrsNotInFields() *tests.Set { "context.http", "context.http.url", "context.http.method", + tests.Group("context.user"), ) } @@ -60,6 +61,8 @@ func spanFieldsNotInPayloadAttrs() *tests.Set { tests.Group("observer"), tests.Group("process"), tests.Group("service"), + tests.Group("user"), + tests.Group("client"), ), // not valid for the span context transactionContext(), @@ -123,6 +126,7 @@ func spanKeywordExceptionKeys() *tests.Set { tests.Group("host"), tests.Group("process"), tests.Group("service"), + tests.Group("user"), ), transactionContext(), ) diff --git a/processor/stream/package_tests/transaction_attrs_test.go b/processor/stream/package_tests/transaction_attrs_test.go index 4b3ac88de5b..3f6371d7425 100644 --- a/processor/stream/package_tests/transaction_attrs_test.go +++ b/processor/stream/package_tests/transaction_attrs_test.go @@ -43,18 +43,19 @@ func transactionPayloadAttrsNotInFields() *tests.Set { return tests.NewSet( tests.Group("transaction.marks."), "transaction.span_count.started", + tests.Group("context.user"), ) } func transactionFieldsNotInPayloadAttrs() *tests.Set { return tests.NewSet( - "context.user.user-agent", - "context.user.ip", "context.http", "context.http.status_code", "host.ip", "transaction.marks.*.*", tests.Group("observer"), + tests.Group("user"), + tests.Group("client"), ) } @@ -97,6 +98,7 @@ func transactionKeywordExceptionKeys() *tests.Set { tests.Group("host"), tests.Group("process"), tests.Group("service"), + tests.Group("user"), ) } @@ -109,7 +111,7 @@ func TestTransactionPayloadMatchFields(t *testing.T) { func TestTransactionPayloadMatchJsonSchema(t *testing.T) { transactionProcSetup().PayloadAttrsMatchJsonSchema(t, transactionPayloadAttrsNotInJsonSchema(), - nil) + tests.NewSet("transaction.context.user.email")) } func TestAttrsPresenceInTransaction(t *testing.T) { diff --git a/processor/stream/processor_test.go b/processor/stream/processor_test.go index eec2f1a7d06..77f458dd503 100644 --- a/processor/stream/processor_test.go +++ b/processor/stream/processor_test.go @@ -146,6 +146,50 @@ func TestIntegration(t *testing.T) { } } +func TestIntegrationRum(t *testing.T) { + report := func(ctx context.Context, p publish.PendingReq) error { + var events []beat.Event + for _, transformable := range p.Transformables { + events = append(events, transformable.Transform(p.Tcontext)...) + } + name := ctx.Value("name").(string) + verifyErr := tests.ApproveEvents(events, name, nil) + if verifyErr != nil { + assert.Fail(t, fmt.Sprintf("Test %s failed with error: %s", name, verifyErr.Error())) + } + return nil + } + + for _, test := range []struct { + path string + name string + }{ + {path: "errors_rum.ndjson", name: "RumErrors"}, + {path: "transactions_spans_rum.ndjson", name: "RumTransactions"}, + } { + t.Run(test.name, func(t *testing.T) { + b, err := loader.LoadDataAsBytes(filepath.Join("../testdata/intake-v2/", test.path)) + require.NoError(t, err) + bodyReader := bytes.NewBuffer(b) + + name := fmt.Sprintf("test_approved_es_documents/testIntakeIntegration%s", test.name) + ctx := context.WithValue(context.Background(), "name", name) + reqTimestamp, err := time.Parse(time.RFC3339, "2018-08-01T10:00:00Z") + ctx = utility.ContextWithRequestTime(ctx, reqTimestamp) + + reqDecoderMeta := map[string]interface{}{ + "user": map[string]interface{}{ + "user-agent": "rum-2.0", + "ip": "192.0.0.1", + }, + } + + actualResult := (&Processor{MaxEventSize: 100 * 1024}).HandleStream(ctx, nil, reqDecoderMeta, bodyReader, report) + assertApproveResult(t, actualResult, test.name) + }) + } +} + func TestRateLimiting(t *testing.T) { report := func(ctx context.Context, p publish.PendingReq) error { return nil diff --git a/processor/stream/test_approved_es_documents/testIntakeIntegrationErrors.approved.json b/processor/stream/test_approved_es_documents/testIntakeIntegrationErrors.approved.json index 0a1c39f5081..8384558a684 100644 --- a/processor/stream/test_approved_es_documents/testIntakeIntegrationErrors.approved.json +++ b/processor/stream/test_approved_es_documents/testIntakeIntegrationErrors.approved.json @@ -65,11 +65,6 @@ }, "tags": { "organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8" - }, - "user": { - "email": "foo@example.com", - "id": 99, - "username": "foo" } }, "error": { @@ -255,6 +250,10 @@ }, "timestamp": { "us": 1494342245999999 + }, + "user": { + "id": "99", + "name": "foo" } }, { @@ -310,6 +309,11 @@ }, "timestamp": { "us": 1533826745999000 + }, + "user": { + "email": "bar@example.com", + "id": "123", + "name": "bar" } }, { @@ -365,6 +369,11 @@ }, "timestamp": { "us": 1533117600000000 + }, + "user": { + "email": "bar@example.com", + "id": "123", + "name": "bar" } }, { @@ -432,6 +441,11 @@ "id": "1234567890987654", "sampled": true, "type": "request" + }, + "user": { + "email": "bar@example.com", + "id": "123", + "name": "bar" } } ] diff --git a/processor/stream/test_approved_es_documents/testIntakeIntegrationMetricsets.approved.json b/processor/stream/test_approved_es_documents/testIntakeIntegrationMetricsets.approved.json index f8ff05fa38e..0ebf60f1c3b 100644 --- a/processor/stream/test_approved_es_documents/testIntakeIntegrationMetricsets.approved.json +++ b/processor/stream/test_approved_es_documents/testIntakeIntegrationMetricsets.approved.json @@ -49,7 +49,12 @@ }, "name": "1234_service-12a3" }, - "short_counter": 227 + "short_counter": 227, + "user": { + "email": "user@mail.com", + "id": "axb123hg", + "name": "logged-in-user" + } }, { "@timestamp": "2017-05-30T18:53:42.281Z", @@ -78,6 +83,11 @@ "name": "ecmascript" }, "name": "1234_service-12a3" + }, + "user": { + "email": "user@mail.com", + "id": "axb123hg", + "name": "logged-in-user" } } ] diff --git a/processor/stream/test_approved_es_documents/testIntakeIntegrationOptionalTimestamps.approved.json b/processor/stream/test_approved_es_documents/testIntakeIntegrationOptionalTimestamps.approved.json index f03b9ab875d..86849416664 100644 --- a/processor/stream/test_approved_es_documents/testIntakeIntegrationOptionalTimestamps.approved.json +++ b/processor/stream/test_approved_es_documents/testIntakeIntegrationOptionalTimestamps.approved.json @@ -65,7 +65,7 @@ "user": { "email": "s@test.com", "id": "123", - "username": "john" + "name": "john" } }, { @@ -153,7 +153,7 @@ "user": { "email": "s@test.com", "id": "123", - "username": "john" + "name": "john" } } ] diff --git a/processor/stream/test_approved_es_documents/testIntakeIntegrationRumErrors.approved.json b/processor/stream/test_approved_es_documents/testIntakeIntegrationRumErrors.approved.json new file mode 100644 index 00000000000..7ebcf4a0d29 --- /dev/null +++ b/processor/stream/test_approved_es_documents/testIntakeIntegrationRumErrors.approved.json @@ -0,0 +1,122 @@ +{ + "events": [ + { + "@timestamp": "2017-12-08T12:18:50.291Z", + "agent": { + "name": "apm-js", + "version": "0.0.0" + }, + "client": { + "ip": "192.0.0.1" + }, + "context": { + "environment": { + "browserHeight": 726, + "browserWidth": 150, + "language": "en-US", + "platform": "MacIntel", + "screenHeight": 800, + "screenWidth": 1280, + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36" + }, + "page": { + "host": "localhost", + "location": "http://localhost:8000/test/e2e/general-usecase/", + "referer": "http://localhost:8000/test/e2e/" + } + }, + "error": { + "culprit": "test/e2e/general-usecase/bundle.js.map", + "exception": { + "message": "Uncaught Error: timeout test error", + "stacktrace": [ + { + "abs_path": "http://localhost:8000/test/../test/e2e/general-usecase/bundle.js.map", + "exclude_from_grouping": false, + "filename": "test/e2e/general-usecase/bundle.js.map", + "function": "\u003canonymous\u003e", + "library_frame": true, + "line": { + "column": 18, + "number": 1 + } + }, + { + "abs_path": "http://localhost:8000/test/./e2e/general-usecase/bundle.js.map", + "exclude_from_grouping": false, + "filename": "~/test/e2e/general-usecase/bundle.js.map", + "function": "invokeTask", + "library_frame": false, + "line": { + "column": 181, + "number": 1 + } + }, + { + "abs_path": "http://localhost:8000/test/e2e/general-usecase/bundle.js.map", + "exclude_from_grouping": false, + "filename": "~/test/e2e/general-usecase/bundle.js.map", + "function": "runTask", + "line": { + "column": 15, + "number": 1 + } + }, + { + "abs_path": "http://localhost:8000/test/e2e/general-usecase/bundle.js.map", + "exclude_from_grouping": false, + "filename": "~/test/e2e/general-usecase/bundle.js.map", + "function": "invoke", + "line": { + "column": 199, + "number": 1 + } + }, + { + "abs_path": "http://localhost:8000/test/e2e/general-usecase/bundle.js.map", + "exclude_from_grouping": false, + "filename": "~/test/e2e/general-usecase/bundle.js.map", + "function": "timer", + "line": { + "column": 33, + "number": 1 + } + } + ], + "type": "Error" + }, + "grouping_key": "52fbc9c2d1a61bf905b4a11c708006fd", + "id": "aba2688e033848ce9c4e4005f1caa534", + "log": { + "message": "Uncaught Error: log timeout test error", + "stacktrace": [ + { + "abs_path": "http://localhost:8000/test/e2e/general-usecase/bundle.js.map", + "exclude_from_grouping": false, + "filename": "~/test/e2e/general-usecase/bundle.js.map", + "function": "\u003canonymous\u003e", + "line": { + "column": 18, + "number": 1 + } + } + ] + } + }, + "processor": { + "event": "error", + "name": "error" + }, + "service": { + "name": "apm-agent-js", + "version": "1.0.1" + }, + "timestamp": { + "us": 1512735530291000 + }, + "user_agent": { + "original": "rum-2.0" + } + } + ] +} diff --git a/processor/stream/test_approved_es_documents/testIntakeIntegrationRumTransactions.approved.json b/processor/stream/test_approved_es_documents/testIntakeIntegrationRumTransactions.approved.json new file mode 100644 index 00000000000..b208ec9ccae --- /dev/null +++ b/processor/stream/test_approved_es_documents/testIntakeIntegrationRumTransactions.approved.json @@ -0,0 +1,132 @@ +{ + "events": [ + { + "@timestamp": "2018-08-01T10:00:00Z", + "agent": { + "name": "apm-js", + "version": "0.0.0" + }, + "client": { + "ip": "192.0.0.1" + }, + "context": { + "_metrics": { + "connectEnd": 14, + "connectStart": 14, + "domComplete": 645, + "domContentLoadedEventEnd": 613, + "domContentLoadedEventStart": 610, + "domInteractive": 610, + "domLoading": 43, + "domainLookupEnd": 14, + "domainLookupStart": 14, + "fetchStart": 5, + "loadEventEnd": 648, + "loadEventStart": 645, + "navigationStart": 0, + "requestStart": 14, + "responseEnd": 32, + "responseStart": 27, + "timeToComplete": 643, + "unloadEventEnd": 32, + "unloadEventStart": 32 + }, + "url": { + "location": "http://localhost:8000/test/e2e/general-usecase/" + } + }, + "processor": { + "event": "transaction", + "name": "transaction" + }, + "service": { + "name": "apm-agent-js", + "version": "1.0.0" + }, + "timestamp": { + "us": 1533117600000000 + }, + "trace": { + "id": "611f4fa950f04631aaaaaaaaaaaaaaaa" + }, + "transaction": { + "duration": { + "us": 643000 + }, + "id": "611f4fa950f04631", + "sampled": true, + "span_count": { + "started": 1 + }, + "type": "page-load" + }, + "user_agent": { + "original": "rum-2.0" + } + }, + { + "@timestamp": "2018-08-01T10:00:00Z", + "agent": { + "name": "apm-js", + "version": "0.0.0" + }, + "context": { + "http": { + "url": "http://localhost:8000/test/e2e/general-usecase/span" + } + }, + "parent": { + "id": "611f4fa950f04631" + }, + "processor": { + "event": "span", + "name": "transaction" + }, + "service": { + "name": "apm-agent-js" + }, + "span": { + "duration": { + "us": 643000 + }, + "hex_id": "aaaaaaaaaaaaaaaa", + "name": "transaction", + "stacktrace": [ + { + "abs_path": "http://localhost:8000/test/e2e/general-usecase/bundle.js.map", + "exclude_from_grouping": false, + "filename": "test/e2e/general-usecase/bundle.js.map", + "function": "\u003canonymous\u003e", + "line": { + "column": 18, + "number": 1 + } + }, + { + "abs_path": "http://localhost:8000/test/e2e/general-usecase/bundle.js.map", + "exclude_from_grouping": false, + "filename": "~/test/e2e/general-usecase/bundle.js.map", + "function": "\u003canonymous\u003e", + "line": { + "column": 18, + "number": 1 + } + } + ], + "start": { + "us": 0 + }, + "type": "transaction" + }, + "timestamp": { + "us": 1533117600000000 + }, + "trace": { + "id": "611f4fa950f04631aaaaaaaaaaaaaaaa" + }, + "transaction": { + "id": "611f4fa950f04631" + } + } + ] +} diff --git a/processor/stream/test_approved_es_documents/testIntakeIntegrationTransactions.approved.json b/processor/stream/test_approved_es_documents/testIntakeIntegrationTransactions.approved.json index 7f03829b29c..6048d4f379b 100644 --- a/processor/stream/test_approved_es_documents/testIntakeIntegrationTransactions.approved.json +++ b/processor/stream/test_approved_es_documents/testIntakeIntegrationTransactions.approved.json @@ -63,6 +63,11 @@ "started": 43 }, "type": "request" + }, + "user": { + "email": "bar@user.com", + "id": "123user", + "name": "bar" } }, { @@ -140,11 +145,6 @@ "tag2": 12, "tag3": 12.45, "tag4": false - }, - "user": { - "email": "foo@example.com", - "id": "99", - "username": "foo" } }, "host": { @@ -203,6 +203,10 @@ "started": 17 }, "type": "request" + }, + "user": { + "id": "99", + "name": "foo" } }, { @@ -283,6 +287,11 @@ "started": 436 }, "type": "request" + }, + "user": { + "email": "bar@user.com", + "id": "123user", + "name": "bar" } } ] diff --git a/processor/stream/test_approved_stream_result/testIntegrationResultRumErrors.approved.json b/processor/stream/test_approved_stream_result/testIntegrationResultRumErrors.approved.json new file mode 100644 index 00000000000..172488d4a5e --- /dev/null +++ b/processor/stream/test_approved_stream_result/testIntegrationResultRumErrors.approved.json @@ -0,0 +1,3 @@ +{ + "accepted": 1 +} diff --git a/processor/stream/test_approved_stream_result/testIntegrationResultRumTransactions.approved.json b/processor/stream/test_approved_stream_result/testIntegrationResultRumTransactions.approved.json new file mode 100644 index 00000000000..9421fa0d0b2 --- /dev/null +++ b/processor/stream/test_approved_stream_result/testIntegrationResultRumTransactions.approved.json @@ -0,0 +1,3 @@ +{ + "accepted": 2 +} diff --git a/testdata/intake-v2/errors.ndjson b/testdata/intake-v2/errors.ndjson index 432ff8467ce..fc782bf3878 100644 --- a/testdata/intake-v2/errors.ndjson +++ b/testdata/intake-v2/errors.ndjson @@ -1,5 +1,5 @@ -{"metadata": {"process": {"ppid": 6789, "pid": 1234, "argv": ["node", "server.js"], "title": "node"}, "system": {"platform": "darwin", "hostname": "prod1.example.com", "architecture": "x64"}, "service": {"name": "1234_service-12a3", "language": {"version": "8", "name": "ecmascript"}, "agent": {"version": "3.14.0", "name": "elastic-node"}, "environment": "staging", "framework": {"version": "1.2.3", "name": "Express"}, "version": "5.1.3", "runtime": {"version": "8.0.0", "name": "node"}}}} -{"error": {"id": "0123456789012345", "timestamp": 1494342245999999, "culprit": "my.module.function_name","log": { "message": "My service could not talk to the database named foobar", "param_message": "My service could not talk to the database named %s", "logger_name": "my.logger.name", "level": "warning", "stacktrace": [ { "abs_path": "/real/file/name.py", "filename": "/webpack/file/name.py", "function": "foo", "vars": { "key": "value" }, "pre_context": ["line1", "line2"], "context_line": "line3","library_frame": false,"lineno": 3,"module": "App::MyModule","colno": 4,"post_context": ["line4","line5" ]},{"filename": "lib/instrumentation/index.js","lineno": 102,"function": "instrumented","abs_path": "/Users/watson/code/node_modules/elastic/lib/instrumentation/index.js","vars": {"key": "value"},"pre_context": [" var trans = this.currentTransaction",""," return instrumented",""," function instrumented () {"," var prev = ins.currentTransaction", " ins.currentTransaction = trans"],"context_line": " var result = original.apply(this, arguments)","post_context": [" ins.currentTransaction = prev"," return result","}","}","","Instrumentation.prototype._recoverTransaction = function (trans) {"," if (this.currentTransaction === trans) return"]}]},"exception": {"message": "The username root is unknown","type": "DbError","module": "__builtins__","code": 42,"handled": false,"attributes": {"foo": "bar" },"stacktrace": [{ "abs_path": "/real/file/name.py","filename": "file/name.py","function": "foo","vars": {"key": "value"},"pre_context": ["line1","line2"],"context_line": "line3", "library_frame": true,"lineno": 3,"module": "App::MyModule","colno": 4,"post_context": ["line4","line5"]},{"filename": "lib/instrumentation/index.js","lineno": 102,"function": "instrumented","abs_path": "/Users/watson/code/node_modules/elastic/lib/instrumentation/index.js","vars": {"key": "value"},"pre_context": [" var trans = this.currentTransaction",""," return instrumented",""," function instrumented () {", " var prev = ins.currentTransaction"," ins.currentTransaction = trans"],"context_line": " var result = original.apply(this, arguments)","post_context": [" ins.currentTransaction = prev"," return result","}","}","","Instrumentation.prototype._recoverTransaction = function (trans) {"," if (this.currentTransaction === trans) return"]}]},"context": {"request": {"socket": {"remote_address": "12.53.12.1","encrypted": true},"http_version": "1.1","method": "POST","url": {"protocol": "https:","full": "https://www.example.com/p/a/t/h?query=string#hash","hostname": "www.example.com","port": "8080","pathname": "/p/a/t/h","search": "?query=string", "hash": "#hash","raw": "/p/a/t/h?query=string#hash"},"headers": {"user-agent": "Mozilla Chrome Edge","content-type": "text/html","cookie": "c1=v1; c2=v2","some-other-header": "foo","array": ["foo","bar","baz"]}, "cookies": {"c1": "v1", "c2": "v2" },"env": {"SERVER_SOFTWARE": "nginx", "GATEWAY_INTERFACE": "CGI/1.1"},"body": "Hello World"},"response": { "status_code": 200, "headers": { "content-type": "application/json" },"headers_sent": true, "finished": true }, "user": { "id": 99, "username": "foo", "email": "foo@example.com"},"tags": {"organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8"}, "custom": {"my_key": 1,"some_other_value": "foo bar","and_objects": {"foo": ["bar","baz" ] }}}}} +{"metadata": {"process": {"ppid": 6789, "pid": 1234, "argv": ["node", "server.js"], "title": "node"}, "user": { "id": 123, "username": "bar", "email": "bar@example.com"}, "system": {"platform": "darwin", "hostname": "prod1.example.com", "architecture": "x64"}, "service": {"name": "1234_service-12a3", "language": {"version": "8", "name": "ecmascript"}, "agent": {"version": "3.14.0", "name": "elastic-node"}, "environment": "staging", "framework": {"version": "1.2.3", "name": "Express"}, "version": "5.1.3", "runtime": {"version": "8.0.0", "name": "node"}}}} +{"error": {"id": "0123456789012345", "timestamp": 1494342245999999, "culprit": "my.module.function_name","log": { "message": "My service could not talk to the database named foobar", "param_message": "My service could not talk to the database named %s", "logger_name": "my.logger.name", "level": "warning", "stacktrace": [ { "abs_path": "/real/file/name.py", "filename": "/webpack/file/name.py", "function": "foo", "vars": { "key": "value" }, "pre_context": ["line1", "line2"], "context_line": "line3","library_frame": false,"lineno": 3,"module": "App::MyModule","colno": 4,"post_context": ["line4","line5" ]},{"filename": "lib/instrumentation/index.js","lineno": 102,"function": "instrumented","abs_path": "/Users/watson/code/node_modules/elastic/lib/instrumentation/index.js","vars": {"key": "value"},"pre_context": [" var trans = this.currentTransaction",""," return instrumented",""," function instrumented () {"," var prev = ins.currentTransaction", " ins.currentTransaction = trans"],"context_line": " var result = original.apply(this, arguments)","post_context": [" ins.currentTransaction = prev"," return result","}","}","","Instrumentation.prototype._recoverTransaction = function (trans) {"," if (this.currentTransaction === trans) return"]}]},"exception": {"message": "The username root is unknown","type": "DbError","module": "__builtins__","code": 42,"handled": false,"attributes": {"foo": "bar" },"stacktrace": [{ "abs_path": "/real/file/name.py","filename": "file/name.py","function": "foo","vars": {"key": "value"},"pre_context": ["line1","line2"],"context_line": "line3", "library_frame": true,"lineno": 3,"module": "App::MyModule","colno": 4,"post_context": ["line4","line5"]},{"filename": "lib/instrumentation/index.js","lineno": 102,"function": "instrumented","abs_path": "/Users/watson/code/node_modules/elastic/lib/instrumentation/index.js","vars": {"key": "value"},"pre_context": [" var trans = this.currentTransaction",""," return instrumented",""," function instrumented () {", " var prev = ins.currentTransaction"," ins.currentTransaction = trans"],"context_line": " var result = original.apply(this, arguments)","post_context": [" ins.currentTransaction = prev"," return result","}","}","","Instrumentation.prototype._recoverTransaction = function (trans) {"," if (this.currentTransaction === trans) return"]}]},"context": {"request": {"socket": {"remote_address": "12.53.12.1","encrypted": true},"http_version": "1.1","method": "POST","url": {"protocol": "https:","full": "https://www.example.com/p/a/t/h?query=string#hash","hostname": "www.example.com","port": "8080","pathname": "/p/a/t/h","search": "?query=string", "hash": "#hash","raw": "/p/a/t/h?query=string#hash"},"headers": {"user-agent": "Mozilla Chrome Edge","content-type": "text/html","cookie": "c1=v1; c2=v2","some-other-header": "foo","array": ["foo","bar","baz"]}, "cookies": {"c1": "v1", "c2": "v2" },"env": {"SERVER_SOFTWARE": "nginx", "GATEWAY_INTERFACE": "CGI/1.1"},"body": "Hello World"},"response": { "status_code": 200, "headers": { "content-type": "application/json" },"headers_sent": true, "finished": true }, "user": { "id": 99, "username": "foo"},"tags": {"organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8"}, "custom": {"my_key": 1,"some_other_value": "foo bar","and_objects": {"foo": ["bar","baz" ] }}}}} { "error": {"id": "cdefab0123456789", "trace_id": null, "timestamp": 1533826745999000,"exception": {"message": "Cannot read property 'baz' no defined"}}} { "error": {"id": "cdefab0123456780", "trace_id": null, "exception": {"type": "DbError"}}} { "error": {"id": "abcdef0123456789", "trace_id": "0123456789abcdeffedcba0123456789", "parent_id": "9632587410abcdef", "transaction_id": "1234567890987654", "transaction": { "sampled": true, "type": "request"}, "timestamp": 1533827045999000,"log": {"level": "custom log level","message": "Cannot read property 'baz' of undefined"}}} diff --git a/testdata/intake-v2/metricsets.ndjson b/testdata/intake-v2/metricsets.ndjson index a6ebbc52f67..295fefec29c 100644 --- a/testdata/intake-v2/metricsets.ndjson +++ b/testdata/intake-v2/metricsets.ndjson @@ -1,3 +1,3 @@ -{"metadata": {"user": null, "process": {"ppid": null, "pid": 1234, "argv": null, "title": null}, "system": null, "service": {"name": "1234_service-12a3", "language": {"version": null, "name":"ecmascript"}, "agent": {"version": "3.14.0", "name": "elastic-node"}, "environment": null, "framework": null,"version": null, "runtime": null}}} +{"metadata": {"user": {"username": "logged-in-user", "id": "axb123hg", "email": "user@mail.com"}, "process": {"ppid": null, "pid": 1234, "argv": null, "title": null}, "system": null, "service": {"name": "1234_service-12a3", "language": {"version": null, "name":"ecmascript"}, "agent": {"version": "3.14.0", "name": "elastic-node"}, "environment": null, "framework": null,"version": null, "runtime": null}}} {"metricset": { "samples": { "byte_counter": { "value": 1 }, "short_counter": { "value": 227 }, "integer_gauge": { "value": 42767 }, "long_gauge": { "value": 3147483648 }, "float_gauge": { "value": 9.16 }, "double_gauge": { "value": 3.141592653589793 }, "dotted.float.gauge": { "value": 6.12 }, "negative.d.o.t.t.e.d": { "value": -1022 } }, "tags": { "some": "abc", "code": 200, "success": true }, "timestamp": 1496170422281000 }} { "metricset": { "samples": { "go.memstats.heap.sys.bytes": { "value": 6.520832e+06 } }, "timestamp": 1496170422281000 }} diff --git a/testdata/intake-v2/transactions.ndjson b/testdata/intake-v2/transactions.ndjson index 555f0e0f31f..1c007463366 100644 --- a/testdata/intake-v2/transactions.ndjson +++ b/testdata/intake-v2/transactions.ndjson @@ -1,4 +1,4 @@ -{"metadata": {"service": {"name": "1234_service-12a3","version": "5.1.3","environment": "staging","language": {"name": "ecmascript","version": "8"},"runtime": {"name": "node","version": "8.0.0"},"framework": {"name": "Express","version": "1.2.3"},"agent": {"name": "elastic-node","version": "3.14.0"}},"process": {"pid": 1234,"ppid": 6789,"title": "node","argv": ["node","server.js"]},"system": {"hostname": "prod1.example.com","architecture": "x64","platform": "darwin"}}} -{ "transaction": { "id": "945254c567a5417e", "trace_id": "0123456789abcdef0123456789abcdef", "parent_id": "abcdefabcdef01234567", "type": "request", "duration": 32.592981, "span_count": { "started": 43 }} } -{"transaction": {"id": "4340a8e0df1906ecbfa9", "trace_id": "0acd456789abcdef0123456789abcdef", "name": "GET /api/types","type": "request","duration": 32.592981,"result": "success", "timestamp": 1496170407154000, "sampled": true, "span_count": {"started": 17},"context": {"request": {"socket": {"remote_address": "12.53.12.1","encrypted": true},"http_version": "1.1","method": "POST","url": {"protocol": "https:","full": "https://www.example.com/p/a/t/h?query=string#hash","hostname": "www.example.com","port": "8080","pathname": "/p/a/t/h","search": "?query=string","hash": "#hash","raw": "/p/a/t/h?query=string#hash"},"headers": {"user-agent": "Mozilla Chrome Edge","content-type": "text/html","cookie": "c1=v1; c2=v2","some-other-header": "foo","array": ["foo","bar","baz"]},"cookies": {"c1": "v1","c2": "v2"},"env": {"SERVER_SOFTWARE": "nginx","GATEWAY_INTERFACE": "CGI/1.1"},"body": {"str": "hello world","additional": { "foo": {},"bar": 123,"req": "additional information"}}},"response": {"status_code": 200,"headers": {"content-type": "application/json"},"headers_sent": true,"finished": true}, "user": {"id": "99","username": "foo","email": "foo@example.com"},"tags": {"organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8", "tag2": 12, "tag3": 12.45, "tag4": false, "tag5": null },"custom": {"my_key": 1,"some_other_value": "foo bar","and_objects": {"foo": ["bar","baz"]},"(": "not a valid regex and that is fine"}}}} +{"metadata": {"service": {"name": "1234_service-12a3","version": "5.1.3","environment": "staging","language": {"name": "ecmascript","version": "8"},"runtime": {"name": "node","version": "8.0.0"},"framework": {"name": "Express","version": "1.2.3"},"agent": {"name": "elastic-node","version": "3.14.0"}},"user": {"id": "123user", "username": "bar", "email": "bar@user.com"}, "process": {"pid": 1234,"ppid": 6789,"title": "node","argv": ["node","server.js"]},"system": {"hostname": "prod1.example.com","architecture": "x64","platform": "darwin"}}} +{"transaction": { "id": "945254c567a5417e", "trace_id": "0123456789abcdef0123456789abcdef", "parent_id": "abcdefabcdef01234567", "type": "request", "duration": 32.592981, "span_count": { "started": 43 }} } +{"transaction": {"id": "4340a8e0df1906ecbfa9", "trace_id": "0acd456789abcdef0123456789abcdef", "name": "GET /api/types","type": "request","duration": 32.592981,"result": "success", "timestamp": 1496170407154000, "sampled": true, "span_count": {"started": 17},"context": {"request": {"socket": {"remote_address": "12.53.12.1","encrypted": true},"http_version": "1.1","method": "POST","url": {"protocol": "https:","full": "https://www.example.com/p/a/t/h?query=string#hash","hostname": "www.example.com","port": "8080","pathname": "/p/a/t/h","search": "?query=string","hash": "#hash","raw": "/p/a/t/h?query=string#hash"},"headers": {"user-agent": "Mozilla Chrome Edge","content-type": "text/html","cookie": "c1=v1; c2=v2","some-other-header": "foo","array": ["foo","bar","baz"]},"cookies": {"c1": "v1","c2": "v2"},"env": {"SERVER_SOFTWARE": "nginx","GATEWAY_INTERFACE": "CGI/1.1"},"body": {"str": "hello world","additional": { "foo": {},"bar": 123,"req": "additional information"}}},"response": {"status_code": 200,"headers": {"content-type": "application/json"},"headers_sent": true,"finished": true}, "user": {"id": "99","username": "foo"},"tags": {"organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8", "tag2": 12, "tag3": 12.45, "tag4": false, "tag5": null },"custom": {"my_key": 1,"some_other_value": "foo bar","and_objects": {"foo": ["bar","baz"]},"(": "not a valid regex and that is fine"}}}} {"transaction": { "id": "cdef4340a8e0df19", "trace_id": "0acd456789abcdef0123456789abcdef", "type": "request", "duration": 13.980558, "timestamp": 1532976822281000, "sampled": null, "span_count": { "dropped": 55, "started": 436 }, "marks": {"navigationTiming": {"appBeforeBootstrap": 608.9300000000001,"navigationStart": -21},"another_mark": {"some_long": 10,"some_float": 10.0}, "performance": {}}, "context": { "request": { "socket": { "remote_address": null, "encrypted": null }, "method": "POST", "headers": { "user-agent": null, "content-type": null, "cookie": null }, "url": { "protocol": null, "full": null, "hostname": null, "port": null, "pathname": null, "search": null, "hash": null, "raw": null } }, "response": { "headers": { "content-type": null } } }} } diff --git a/testdata/intake-v2/transactions_spans.ndjson b/testdata/intake-v2/transactions_spans.ndjson index 44c6ffa3e40..72cca922bc2 100644 --- a/testdata/intake-v2/transactions_spans.ndjson +++ b/testdata/intake-v2/transactions_spans.ndjson @@ -1,5 +1,5 @@ -{"metadata":{"service":{"name":"1234_service-12a3","version":"5.1.3","environment":"staging","language":{"name":"ecmascript","version":"8"},"runtime":{"name":"node","version":"8.0.0"},"framework":{"name":"Express","version":"1.2.3"},"agent":{"name":"elastic-node","version":"3.14.0"}},"process":{"pid":1234,"ppid":6789,"title":"node","argv":["node","server.js"]},"system":{"hostname":"prod1.example.com","architecture":"x64","platform":"darwin"}}} -{"transaction":{"id":"945254c567a5417e","name":"GET /api/types","type":"request","duration":32.592981,"result":"success","timestamp":1496170407154000,"sampled":true,"span_count":{"dropped":2,"started":4},"context":{"request":{"socket":{"remote_address":"12.53.12.1","encrypted":true},"http_version":"1.1","method":"POST","url":{"protocol":"https:","full":"https://www.example.com/p/a/t/h?query=string#hash","hostname":"www.example.com","port":"8080","pathname":"/p/a/t/h","search":"?query=string","hash":"#hash","raw":"/p/a/t/h?query=string#hash"},"headers":{"user-agent":"Mozilla Chrome Edge","content-type":"text/html","cookie":"c1=v1; c2=v2","some-other-header":"foo","array":["foo","bar","baz"]},"cookies":{"c1":"v1","c2":"v2"},"env":{"SERVER_SOFTWARE":"nginx","GATEWAY_INTERFACE":"CGI/1.1"},"body":{"str":"hello world","additional":{"foo":{},"bar":123,"req":"additional information"}}},"response":{"status_code":200,"headers":{"content-type":"application/json"},"headers_sent":true,"finished":true},"user":{"id":"99","username":"foo","email":"foo@example.com"},"tags":{"organization_uuid":"9f0e9d64-c185-4d21-a6f4-4673ed561ec8", "number_code": 2, "bool_error": false},"custom":{"my_key":1,"some_other_value":"foo bar","and_objects":{"foo":["bar","baz"]},"(":"not a valid regex and that is fine"}},"marks":{"navigationTiming":{"appBeforeBootstrap":608.9300000000001,"navigationStart":-21},"another_mark":{"some_long":10,"some_float":10.0},"performance":{}},"trace_id":"945254c567a5417eaaaaaaaaaaaaaaaa"}} +{"metadata":{"service":{"name":"1234_service-12a3","version":"5.1.3","environment":"staging","language":{"name":"ecmascript","version":"8"},"runtime":{"name":"node","version":"8.0.0"},"framework":{"name":"Express","version":"1.2.3"},"agent":{"name":"elastic-node","version":"3.14.0"}},"process":{"pid":1234,"ppid":6789,"title":"node","argv":["node","server.js"]},"system":{"hostname":"prod1.example.com","architecture":"x64","platform":"darwin"},"user": {"id": "123user", "username": "foo", "email": "foo@bar.com"}}} +{"transaction":{"id":"945254c567a5417e","name":"GET /api/types","type":"request","duration":32.592981,"result":"success","timestamp":1496170407154000,"sampled":true,"span_count":{"dropped":2,"started":4},"context":{"request":{"socket":{"remote_address":"12.53.12.1","encrypted":true},"http_version":"1.1","method":"POST","url":{"protocol":"https:","full":"https://www.example.com/p/a/t/h?query=string#hash","hostname":"www.example.com","port":"8080","pathname":"/p/a/t/h","search":"?query=string","hash":"#hash","raw":"/p/a/t/h?query=string#hash"},"headers":{"user-agent":"Mozilla Chrome Edge","content-type":"text/html","cookie":"c1=v1; c2=v2","some-other-header":"foo","array":["foo","bar","baz"]},"cookies":{"c1":"v1","c2":"v2"},"env":{"SERVER_SOFTWARE":"nginx","GATEWAY_INTERFACE":"CGI/1.1"},"body":{"str":"hello world","additional":{"foo":{},"bar":123,"req":"additional information"}}},"response":{"status_code":200,"headers":{"content-type":"application/json"},"headers_sent":true,"finished":true},"user":{"id":"99","email":"foo@example.com"},"tags":{"organization_uuid":"9f0e9d64-c185-4d21-a6f4-4673ed561ec8", "number_code": 2, "bool_error": false},"custom":{"my_key":1,"some_other_value":"foo bar","and_objects":{"foo":["bar","baz"]},"(":"not a valid regex and that is fine"}},"marks":{"navigationTiming":{"appBeforeBootstrap":608.9300000000001,"navigationStart":-21},"another_mark":{"some_long":10,"some_float":10.0},"performance":{}},"trace_id":"945254c567a5417eaaaaaaaaaaaaaaaa"}} {"span":{"id":"0aaaaaaaaaaaaaaa","timestamp":1496170407154000,"parent":null,"name":"SELECT FROM product_types","type":"db.postgresql.query","start":2.83092,"duration":3.781912,"sync":false,"stacktrace":[{"function":"onread","abs_path":"net.js","filename":"net.js","lineno":547,"library_frame":true,"vars":{"key":"value"},"module":"some module","colno":4,"context_line":"line3","pre_context":[" var trans = this.currentTransaction",""],"post_context":[" ins.currentTransaction = prev"," return result","}"]},{"filename":"my2file.js","lineno":10}],"context":{"db":{"instance":"customers","statement":"SELECT * FROM product_types WHERE user_id=?","type":"sql","user":"readonly_user"},"http":{"url":"http://localhost:8000","status_code":200,"method":"GET"},"tags":{"span_tag":"something"}},"transaction_id":"945254c567a5417e","parent_id":"945254c567a5417e","trace_id":"945254c567a5417eaaaaaaaaaaaaaaaa"}} {"span":{"id":"1aaaaaaaaaaaaaaa","timestamp":1496170407154000,"parent":0,"name":"GET /api/types","type":"request","start":0,"duration":32.592981,"transaction_id":"945254c567a5417e","parent_id":"945254c567a5417e","trace_id":"945254c567a5417eaaaaaaaaaaaaaaaa"}} {"span":{"id":"2aaaaaaaaaaaaaaa","timestamp":1496170407154000,"parent":1,"name":"GET /api/types","type":"request","start":1.845,"duration":3.5642981,"stacktrace":[],"context":{},"transaction_id":"945254c567a5417e","parent_id":"945254c567a5417e","trace_id":"945254c567a5417eaaaaaaaaaaaaaaaa"}} diff --git a/tests/system/error.approved.json b/tests/system/error.approved.json index ba7a63f4d91..0097f8f25a9 100644 --- a/tests/system/error.approved.json +++ b/tests/system/error.approved.json @@ -56,6 +56,11 @@ }, "grouping_key": "18f82051862e494727fa20e0adc15711", "id": "7f0e9d68c1854d21a6f44673ed561ec8" + }, + "user": { + "username": "bar", + "id": 123, + "email": "bar@example.com" } }, "_index": "test-apm-12-12-2017" @@ -110,6 +115,11 @@ "transaction": { "id": "945254c5-67a5-417e-8a4e-aa29efcbfb79" }, + "user": { + "username": "foo", + "id": 99, + "email": "foo@example.com" + }, "context": { "tags": { "organization_uuid": "9f0e9d64-c185-4d21-a6f4-4673ed561ec8" @@ -162,11 +172,6 @@ "my_key": 1, "some_other_value": "foo bar" }, - "user": { - "username": "foo", - "id": 99, - "email": "foo@example.com" - }, "response": { "headers": { "content-type": "application/json" @@ -322,6 +327,11 @@ "processor": { "name": "error", "event": "error" + }, + "user": { + "username": "bar", + "id": 123, + "email": "bar@example.com" } }, "_index": "test-apm-12-12-2017" @@ -384,6 +394,11 @@ }, "grouping_key": "f6b5a2877d9b00d5b32b44c9db039f11", "id": "8f0e9d68c1854d21a6f44673ed561ec8" + }, + "user": { + "username": "bar", + "id": 123, + "email": "bar@example.com" } }, "_index": "test-apm-12-12-2017" @@ -446,6 +461,11 @@ "message": "Cannot read property 'baz' of undefined", "level": "custom log level" } + }, + "user": { + "username": "bar", + "id": 123, + "email": "bar@example.com" } }, "_index": "test-apm-12-12-2017" diff --git a/tests/system/test_integration.py b/tests/system/test_integration.py index 82bfff0d661..aebe2266f33 100644 --- a/tests/system/test_integration.py +++ b/tests/system/test_integration.py @@ -190,20 +190,19 @@ def test_enrich_backend_event(self): assert "ip" in rs['hits']['hits'][0]["_source"]["host"], rs['hits'] @unittest.skipUnless(INTEGRATION_TESTS, "integration test") - @unittest.skip("WIP") def test_enrich_rum_event(self): self.load_docs_with_template(self.get_error_payload_path(), self.intake_url, 'error', 1) - rs = self.es.search(index=self.index_name, body={ - "query": {"term": {"processor.event": "error"}}}) + rs = self.es.search(index=self.index_name, body={"query": {"term": {"processor.event": "error"}}}) hits = rs['hits']['hits'] for hit in hits: - assert "ip" in hit["_source"]["context"]["user"], rs['hits'] - assert "user-agent" in hit["_source"]["context"]["user"], rs['hits'] + assert "user_agent" in hit["_source"], rs['hits'] + assert "original" in hit["_source"]["user_agent"], rs['hits'] + assert "ip" in hit["_source"]["client"], rs['hits'] @unittest.skipUnless(INTEGRATION_TESTS, "integration test") def test_grouping_key_for_error(self): diff --git a/tests/system/transaction.approved.json b/tests/system/transaction.approved.json index 21cb74900d4..f7db6d1c71c 100644 --- a/tests/system/transaction.approved.json +++ b/tests/system/transaction.approved.json @@ -6,17 +6,17 @@ "_source": { "@timestamp": "2017-05-30T18:53:27.154Z", "agent": { - "name": "elastic-node", + "name": "1elastic-node", "version": "3.14.0" }, "host": { "architecture": "x64", "hostname": "prod1.example.com", "platform": "darwin", - "ip": "127.0.0.1" + "ip": "1127.0.0.1" }, "observer": { - "name": "ed7e2cf02cd9", + "name": "123", "version": "7.0.0-alpha1", "hostname": "ed7e2cf02cd9" }, @@ -29,13 +29,13 @@ "node", "server.js" ], - "pid": 1234, + "pid": 11234, "title": "node", "ppid": 6789 }, "service": { "runtime": { - "name": "node", + "name": "1node", "version": "8.0.0" }, "framework": { @@ -50,6 +50,10 @@ "environment": "staging", "name": "1234_service-12a3" }, + "user": { + "id": "199", + "email": "foo@example.com" + }, "context": { "custom": { "some_other_value": "foo bar", @@ -62,11 +66,6 @@ "my_key": 1, "(": "not a valid regex and that is fine" }, - "user": { - "id": "99", - "username": "foo", - "email": "foo@example.com" - }, "request": { "cookies": { "c2": "v2", @@ -220,6 +219,11 @@ "us": 13980 }, "result": "200" + }, + "user": { + "email": "bar@user.com", + "id": "123user", + "name": "bar" } }, "_index": "test-apm-12-12-2017" @@ -287,6 +291,11 @@ "us": 13980 }, "result": "200" + }, + "user": { + "email": "bar@user.com", + "id": "123user", + "name": "bar" } }, "_index": "test-apm-12-12-2017" @@ -354,6 +363,11 @@ }, "name": "GET /api/types", "result": "failure" + }, + "user": { + "email": "bar@user.com", + "id": "123user", + "name": "bar" } }, "_index": "test-apm-12-12-2017" diff --git a/utility/map_str_enhancer.go b/utility/map_str_enhancer.go index bc8c19e9b73..7da76186e01 100644 --- a/utility/map_str_enhancer.go +++ b/utility/map_str_enhancer.go @@ -159,6 +159,16 @@ func MergeAdd(m common.MapStr, key string, val common.MapStr) { } } +func AddIfNil(m common.MapStr, key string, val common.MapStr) { + if m == nil || val == nil || len(val) == 0 { + return + } + if _, ok := m[key]; ok { + return + } + Add(m, key, val) +} + func MillisAsMicros(ms float64) common.MapStr { m := common.MapStr{} m["us"] = int(ms * 1000)