chili_logger 0.0.5 → 0.0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.byebug_history +78 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +10 -2
  5. data/README.md +1045 -30
  6. data/assets/images/chili-logger-flow.png +0 -0
  7. data/assets/images/chiligum-logging-system.gif +0 -0
  8. data/assets/images/rabbit-basic-functioning.png +0 -0
  9. data/assets/images/rabbit-topic-explanation.webp +0 -0
  10. data/chili_logger-0.0.6.gem +0 -0
  11. data/chili_logger-0.0.7.gem +0 -0
  12. data/chili_logger-0.0.8.gem +0 -0
  13. data/chili_logger-0.0.9.gem +0 -0
  14. data/chili_logger.gemspec +1 -1
  15. data/lib/brokers/rabbit_broker.rb +5 -10
  16. data/lib/{errors/logging_error_handler/sqs_handler.rb → brokers/sqs_broker.rb} +4 -14
  17. data/lib/chili_logger.rb +33 -14
  18. data/lib/chili_logger/version.rb +1 -1
  19. data/lib/current_log_accessor.rb +77 -53
  20. data/lib/errors/logging_error_handler/logging_error_handler.rb +23 -14
  21. data/lib/{logs_coverage → helpers/logs_coverage}/chili_logger_coverage.yml +0 -0
  22. data/lib/{logs_coverage → helpers/logs_coverage}/coverage_writer.rb +20 -14
  23. data/lib/{logs_coverage → helpers/logs_coverage}/levantamento_provis/303/263rio.yaml +0 -0
  24. data/lib/helpers/values/default.rb +92 -0
  25. data/lib/helpers/values/type_uniformizer/desc.rb +64 -0
  26. data/lib/helpers/values/type_uniformizer/main_content.rb +62 -0
  27. data/lib/helpers/values/type_uniformizer/user.rb +43 -0
  28. data/lib/message_writer/aws_ops_metadata.rb +1 -1
  29. data/lib/message_writer/message_writer.rb +21 -18
  30. data/log/chili-logger-coverage.yml +50 -1
  31. metadata +26 -11
  32. data/chili_logger-0.0.1.gem +0 -0
  33. data/chili_logger-0.0.3.gem +0 -0
  34. data/lib/errors/error_messages/rabbitmq_error.rb +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0baddcb177c1e5a6676aa3a60040e37b17397958348944ff4f0b980b4c37bef2
4
- data.tar.gz: 8511fd8734a086ec0b8fd82dea6329aa8e4bd351f6daa0405fa519d29b401c2c
3
+ metadata.gz: bcee860ae763961cedf6bd91456a7932a00ca2eae59f276380c2a90dd9479e12
4
+ data.tar.gz: cb5e2cc3075bbda8cba9baa7f29ae05f5f35b79eb3f7a986bc382dcba7052f03
5
5
  SHA512:
6
- metadata.gz: 90f1f25ebd08ea66819dd74b86d05a073de1d317b7df3428e4bcda6ecfd5f94f7db5d907acbeed7a2ba5b7555a50e07e9a2d12d1a1217ab0571a4b03f8012318
7
- data.tar.gz: 9319b3f0ffb1daf8800615873068bd96a4502424452009ca4a3683c776a3a4cd7231ff162c7bb68ec563898202436062d0cea0da3c4a9361db432a3b260e9527
6
+ metadata.gz: 2492803a054170942b232fd1186827ed1b4f635b6a5fc7de7883321be454756b841502afe744112e11b398fd1b27eaf4813fc5d10db12018e22e81d7534da443
7
+ data.tar.gz: f9e3ebd7f7414863fa78ab5affac2b9940fcd40a07a85489afdb3782869d1ce098d373ac861cdb4eed7259aa4b0575331a2f2bdae024953068d0cc9344e835cb
@@ -1,4 +1,82 @@
1
1
  continue
2
+ options
3
+ continue
4
+ options
5
+ continue
6
+ @desc_uniformizer.desc([])
7
+ @desc_uniformizer.desc(options[:desc])
8
+ @desc_uniformizer.desc
9
+ @desc_uniformizer.desc(options[:desc])
10
+ options
11
+ continue
12
+ options
13
+ continue
14
+ main_content.merge(uniformized_content)
15
+ main_content
16
+ continue
17
+ main_content
18
+ uniformized_content
19
+ continue
20
+ desc
21
+ continue
22
+ uniformized_content
23
+ continue
24
+ uniformized_content
25
+ quit
26
+ val.first.is_a?(Hash)
27
+ val.first.is_a?(Hash) ? {} : record
28
+ modified_record(val.first)
29
+ val
30
+ uniformized_val
31
+ quit
32
+ continue
33
+ error.inspect.is_a?(String)
34
+ error.inspect
35
+ error.class.ancestors.include?(Exception)
36
+ error
37
+ new_error
38
+ errors
39
+ all_errors
40
+ continue
41
+ new_error.is_a?(String)
42
+ continue
43
+ new_error.is_a?(String)
44
+ new_error.class
45
+ new_error
46
+ continue
47
+ new_error
48
+ continue
49
+ new_error
50
+ continue
51
+ error
52
+ new_error
53
+ quit
54
+ main_content[:errors] ||= @default.log_errors
55
+ errors
56
+ continue
57
+ user[:cognito_id]
58
+ new_user[:cognito_id]
59
+ continuenew_user[:cognito_id]
60
+ continue
61
+ new_user
62
+ continue
63
+ user
64
+ Thread.current[:current_log_user]
65
+ Thread.current[:current_log_user] ||= default_user
66
+ continue
67
+ Thread.current[:current_log_user] ||= default_user
68
+ continue
69
+ Thread.current[:current_log_user] ||= default_user
70
+ continue
71
+ Thread.current[:current_log_user] ||= default_user
72
+ user
73
+ continue
74
+ log_user
75
+ continue
76
+ Thread.current[:current_log_user] ||= default_user
77
+ Thread.current[:current_log_user]
78
+ user
79
+ continue
2
80
  e.class
3
81
  e.is_a?(Bunny:TCPConnectionFailed)
4
82
  e
data/Gemfile CHANGED
@@ -12,3 +12,5 @@ gem 'rspec', '~> 3.0'
12
12
  gem 'rubocop', '~> 0.60.0', require: false
13
13
 
14
14
  gem 'httparty', '~> 0.18.1'
15
+
16
+ gem "simplecov", "~> 0.17.1"
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- chili_logger (0.0.5)
5
- aws-sdk
4
+ chili_logger (0.0.9.1)
5
+ aws-sdk (~> 2.9, >= 2.9.0)
6
6
  bunny
7
7
  httparty
8
8
 
@@ -38,6 +38,7 @@ GEM
38
38
  byebug (11.0.1)
39
39
  concurrent-ruby (1.1.6)
40
40
  diff-lcs (1.4.2)
41
+ docile (1.3.2)
41
42
  httparty (0.18.1)
42
43
  mime-types (~> 3.0)
43
44
  multi_xml (>= 0.5.2)
@@ -45,6 +46,7 @@ GEM
45
46
  concurrent-ruby (~> 1.0)
46
47
  jaro_winkler (1.5.4)
47
48
  jmespath (1.4.0)
49
+ json (2.3.1)
48
50
  mime-types (3.3.1)
49
51
  mime-types-data (~> 3.2015)
50
52
  mime-types-data (3.2020.0512)
@@ -78,6 +80,11 @@ GEM
78
80
  ruby-progressbar (~> 1.7)
79
81
  unicode-display_width (~> 1.4.0)
80
82
  ruby-progressbar (1.10.1)
83
+ simplecov (0.17.1)
84
+ docile (~> 1.1)
85
+ json (>= 1.8, < 3)
86
+ simplecov-html (~> 0.10.0)
87
+ simplecov-html (0.10.2)
81
88
  thread_safe (0.3.6)
82
89
  tzinfo (1.2.7)
83
90
  thread_safe (~> 0.1)
@@ -96,6 +103,7 @@ DEPENDENCIES
96
103
  rake (~> 12.0)
97
104
  rspec (~> 3.0)
98
105
  rubocop (~> 0.60.0)
106
+ simplecov (~> 0.17.1)
99
107
 
100
108
  BUNDLED WITH
101
109
  1.17.3
data/README.md CHANGED
@@ -1,27 +1,271 @@
1
1
  # ChiliLogger
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/chili_logger`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ ChiliLogger is a gem developed by Chiligum Creatives for internal use. It is used to monitor our ruby applications and generate logs with some uniformization.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ ## Table of Contents
6
+ * [READ BEFORE CONTRIBUTING](##read-before-contributing)
7
+ * [How it Works](##how-it-works)
8
+ * [Overview](###overview)
9
+ * [Anatomy of a Chiligum Log](###anatomy-of-a-chiligum-log)
10
+ * [Example Log](####example-log)
11
+ * [Environment](####environment)
12
+ * [Layer](####layer)
13
+ * [Type](####type)
14
+ * [Service](####service)
15
+ * [Action](####action)
16
+ * [Description](####description)
17
+ * [User](####user)
18
+ * [Main Content](####main-content)
19
+ * [Ops Metadata](####ops-metadata)
20
+ * [Timestamp](####timestamp)
21
+ * [A Primer on RabbitMQ and ElasticSearch](###a-primer-on-rabbitmq-and-elasticsearch)
22
+ * [RabbitMQ](####rabbitmq)
23
+ * [ElasticSearch](####elasticsearch)
24
+ * [Putting It All Together](####putting-it-all-together)
25
+ * [Installation](##installation)
26
+ * [Basic Usage](##basic-usage)
27
+ * [publish_instant_log params](####publish_instant_log-params)
28
+ * [publish_instant_log usage example](####publish_instant_log-usage-example)
29
+ * [publish_instant_log default argument values](####publish_instant_log-default-argument-values)
30
+ * [ChiliLogger's Singleton pattern](####ChiliLoggers-singleton-pattern)
31
+ * [Advanced Usage](##advanced-usage)
32
+ * [Code Example](####advanced-code-example)
33
+ * [Error Logging](####error-logging)
34
+ * [Customizing a Started Log](####customizing-a-started-log)
35
+ * [Accessing the current_log](####accessing-the-current_log)
36
+ * [user](####user)
37
+ * [update_user](####update_user)
38
+ * [desc](####desc)
39
+ * [update_type](####update_type)
40
+ * [update_service](####update_service)
41
+ * [update_action](####update_action)
42
+ * [update_desc](####update_desc)
43
+ * [main_content](####main_content)
44
+ * [update_main_content](####update_main_content)
45
+ * [add_to_main_content](####add_to_main_content)
46
+ * [modified_records](####modified_records)
47
+ * [update_modified_records](####update_modified_records)
48
+ * [add_modified_record](####add_modified_record)
49
+ * [clear_log_info](####clear_log_info)
50
+ * [Snippets For Quicker Logging](##snippets-for-quicker-logging)
51
+ * [Papertrail Optional Use](###papertrail-optional-use)
52
+ * [Logging Transactions in HTP Requests](###logging-transactions-in-http-requests)
53
+ * [Controllers](####controllers-logging-logic)
54
+ * [Models](####models-logging-logic)
55
+ * [Logging Rake Tasks](###logging-rake-tasks)
56
+ * [Logging Uncaught task Errors](####logging-uncaught-task-errors)
57
+ * [Logging Transactions in Tasks](####logging-transactions-in-tasks)
58
+ * [Logging Heartbeats in Tasks](####logging-heartbeats-in-tasks)
59
+ * [Coverage](##Coverage)
6
60
 
7
- ## Installation
61
+ ## READ BEFORE CONTRIBUTING
62
+ The main thing you need to know before changing Chililogger's source code is this: we send our logs in JSON format and [Logstash parses and converts them to Elasticsearch objects](####elasticsearch), requiring all fields to have consistent primitive types. So suppose you send these logs:
63
+ ```json
64
+ // first log
65
+ {
66
+ "user": {
67
+ "name": "Joe Doe"
68
+ }
69
+ }
8
70
 
9
- Add this line to your application's Gemfile:
71
+ // second log
72
+ {
73
+ "user": "Mary Jane"
74
+ }
75
+
76
+ // third log
77
+ {
78
+ "user": {
79
+ "name": {
80
+ "first": "Francis",
81
+ "last": "Coppola"
82
+ }
83
+ }
84
+ }
85
+
86
+ // fourth log
87
+ {
88
+ "client": {
89
+ "name": "Angela"
90
+ }
91
+ }
92
+ ```
93
+ After receiving the first log, `Logstash` will parse it and define that the `user` field is always an object and that it **may** have a field called `name`, which is a string. Notice that it is not mandatory for logs to have a `user` and a `user.name`. But, if a log does have these fields, then they **must** be an object and a string, respectively.
94
+
95
+ When Logstash receives the second log, it will see an inconsistency, because this time `user` is a string, not an object. Because of that, Logstash will ignore this log - in other words, **this log will not be saved to Elasticsearch**. When the third log arrives, the same will happen. It will see that `user` is consistent with what it expected, but that the field `user.name` is now an object, intead of being a string as was expected. This log will also be ignored by Logstash. The fourth log would be accepted though, since `client.name` hasn't been set yet.
96
+
97
+ So if you are going to change anything in ChiliLogger, you have to keep this in mind: **the primitive types of fields in an Elasticsearch index must always be consistent**. If your changes generate logs with fields that already existed in the Elasticsearch index, but now have a different primitive type, Logstash will ignore these logs. This is important because Logstash just ignores incorrect logs without raising errors - so it may take a long time before the problem is noticed. If you need to change the type of a previously set field, you will need to create a new index/Logstash and make sure the logs are sent to this new Logstash.
98
+
99
+ ## How it works
100
+
101
+ ### Overview
102
+
103
+ ![ChiliLogger usual Flow](assets/images/chili-logger-flow.png)
104
+
105
+ For the time being, only `RabbitMQ` is supported as the main message broker. Only `AWS SQS` is available as a fallback broker.
106
+
107
+ If both the main message broker and the fallback message broker are down, ChiliLogger wil discard the log to keep the application running without raising any error. That's because raising errors would make the app crash and make it impossible to be used while the brokers are down.
108
+
109
+
110
+ ### Anatomy of a Chiligum Log
111
+
112
+ The ChiliLogger gem was created to guarantee some level of uniformization in logs generated by all our ruby applications. So, to better understand its functioning and usage, it is helpful to first understand what kind of logs it is trying to generate. An example log would look like the following:
10
113
 
114
+ #### Example Log
115
+ ```json
116
+ {
117
+ "env": "development",
118
+ "layer": "creatives",
119
+ "type": "transaction",
120
+ "service": "videos",
121
+ "action": "create",
122
+ "desc": "development.creatives.transaction.videos.create",
123
+ "user": {
124
+ "cognito_id": "88",
125
+ "email": 'exemple@test.com',
126
+ "company_cognito_id": "75",
127
+ "company_name": "Chiligum Creatives",
128
+ "ghost_user_cognito_id": "not_specified"
129
+ },
130
+ "transaction": {
131
+ "modified_records": {
132
+ "videos": [{ "id": 42, "title": 'Lateralus' }],
133
+ "gallery_tags": [{ "id": 50 }, { "id": 51 }, { "id": 52 }],
134
+ "tags": [{ "id": 23, "name": "tool" }]
135
+ },
136
+ "errors": [],
137
+ "backtrace": [
138
+ "app/views/dashboard/controllers/videos_controller.rb:17 in block 'create'"
139
+ ],
140
+ },
141
+ "ops_metadata": {
142
+ "server_url": "https://app.chiligumvideos.com"
143
+ },
144
+ "timestamp": "2020-06-30T18:08:59-03:00"
145
+ }
146
+
147
+ ```
148
+ Below is a short explanation of each one of the log attributes.
149
+
150
+ #### Environment
151
+ The `env` attribute is used for tagging the log and tells in which environment the app was running when the log was created. We are mainly concerned with three possible environments: `production`, `staging`, and `development`. Needless to say, logs from a production environment are the most important to us, but staging and development logs can be useful for debugging/testing.
152
+
153
+ ---
154
+
155
+ #### Layer
156
+ The `layer` attribute is also used for tagging and tells us in which layer of the Chiligum platform the log was created. Usually, the layer will correspond to the name of the application/service where ChiliLogger was installed. For instance, it could be 'creatives', 'api', 'render', etc...
157
+
158
+ ---
159
+
160
+ #### Type
161
+ Also a tagging attribute, tells what type of log it is. Chiligum has 3 main types of logs:
162
+ - `transaction` is used for logging transactions made by users, such as creating a campaign, uploading a file to the gallery, etc...
163
+ - `uncaught_error` is used for logging internal server errors and to help us find problematic points in our applications;
164
+ - `monitoring` is used to monitor the health of our servers, providing information such as CPU usage, uptime, etc...
165
+
166
+ ChiliLogger is concerned only with `transaction` and `uncaught_errors`, since they are the only ones that can be generated in the application itself. Monitoring logs are created by other monitoring services, like ElasticSearch Beats or AWS Cloudwatch.
167
+
168
+ ---
169
+
170
+ #### Service
171
+ The `service` tagging attribute is used to tell what group of features the user was trying to interact with. Services can be things like 'videos', 'campaigns', 'banners', 'gallery' and will usually have the similar names to the Controllers in an application.
172
+
173
+ ---
174
+
175
+ #### Action
176
+ The `action` tagging attribute describes what action exactly the user was trying to perform in our platform. Usual values for it are 'create', 'update, and 'delete'. Usually, action tags will have similar names to Controllers' methods.
177
+
178
+ ---
179
+
180
+ #### Description
181
+ The `desc` is the last of the tagging attributes and is used to combine all of the previous attributes in a single tag, allowing any person that is examining a log to quickly identify what the log is about. It is structured in the following manner:
11
182
  ```ruby
12
- gem 'chili_logger'
183
+ "#{env}.#{layer}.#{type}.#{service}.#{action}"
13
184
  ```
185
+ Notice that the tags are separated by a `.` period. That's because the `desc` tag is also used to create the [routing_key for topic exchanges](https://www.rabbitmq.com/tutorials/amqp-concepts.html#exchange-topic) when ChiliLogger is using RabbitMQ as its MessageBroker.
14
186
 
15
- And then execute:
187
+ ---
16
188
 
17
- $ bundle install
189
+ #### User
190
+ The `user` stores the main infos about the user whose request generated the log. The user is an object that accepts five attributes:
191
+ - `cognito_id`, the user's global ID, the one the identifies it in Cognito's DB;
192
+ - `email`, so we can easily know who the user is;
193
+ - `company_cognito_id`, the global ID of the user's company;
194
+ - `company_name`;
195
+ - `ghost_user_cognito_id`, for cases when admin users can log as other users. In these cases, the admin's global ID should be stored in the ghost_user_cognito_id;
196
+
197
+ ---
198
+
199
+ #### Main Content
200
+ ##### (Transaction | Error)
201
+ The `main_content` will have all other data relevant to the log. The main content of a log will be stored in a key with the same name as the log `type`. So a transaction log, as in the example above, willhave its main_content stored in the `transaction` field. In an error log, it would be in an `error` field.
202
+ It is an object with that accepts the folowwing attributes:
203
+ - `modified_records`, listing all tables modified and the records themselves;
204
+ - `errors`, listing errors found during the transaction and will usually be an empty array if the transaction was successful;
205
+ - `backtrace`, automatically created by the gem itself and showing the backtrace of the transaction and its processing;
206
+
207
+ ---
208
+
209
+ #### Ops Metadata
210
+ Based on the configuration data(`:server_url`, `:cloud_provider`) that is passed to ChiliLogger when it is initialized, the log will also have metadata about the server itself where the application is running.
211
+
212
+ ---
213
+
214
+ #### Timestamp
215
+ ChiliLogger automatically adds a `timestamp` to the log.
216
+
217
+ ### A Primer on RabbitMQ and ElasticSearch
218
+ Since Chiligum's logging system is entirely based on RabbitMQ and ElasticSearch, it's useful to have at least a high level understanding of how they work and how they are being used together.
219
+
220
+ #### RabbitMQ
221
+ RabbitMQis a popular message broker that can handle a high traffic of messages. Its role in our logging system is to simply receive all incoming logs and put them in appropriate queues, so other services can have access to these logs and process and/or store them in a central database.
222
+
223
+ RabbitMQ has 4 main entities:
224
+ * `publishers`, which create the messages (in our case, it's ChiliLogger creating the logs);
225
+ * `exchanges`, where publishers send the messages to;
226
+ * `queues`, which receive just the messages from the exchange that are relevant to them.
227
+ * `consumers`, which get messages from specific queues (in our case, that's Logstash/Elasticsearch)
228
+
229
+ ![explanation for RabbitMQ basic functioning](assets/images/rabbit-basic-functioning.png)
230
+
231
+ All messages are published to an exchange with a `routing_key`, which allows the exchange to know which queues should receive that message. RabbitMQ has a couple of possible exchange types and each one of them has a different way of distributing the messages among the queues. At Chiligum, we use only the `topic` type, the most flexible one and the one that can simulate all other types if needed.
232
+
233
+ In topic exchanges, the routing_key will be composed of keywords separated by dots ("."). Queues, on the other hand, will define which keywords they are interested at by defining their `binding_keys`. So a message with `production.creatives.videos` as routing_key would bedelivered to any queue with a matching binding_key of `production.creatives.videos`. Queues can use wildcards(`*`, `#`) in their binding keys. It's easier to understand it with the exemple from the RabbitMQ tutorials itself:
234
+
235
+ > `*` can substitute for exactly one word. `#` can substitute for zero or more words.
236
+ >
237
+ > In this example, we're going to send messages which all describe animals. The messages will be sent with a routing key that consists of three words (two dots). The first word in the routing key will describe a celerity, second a colour and third a species: "`<celerity>.<colour>.<species>`".
238
+
239
+ ![explanation for RabbitMQ topic excahnges](assets/images/rabbit-topic-explanation.webp)
240
+ >
241
+ > We created three bindings: Q1 is bound with binding key "`*.orange.*`" and Q2 with "`*.*.rabbit`" and "`lazy.#`". These bindings can be summarised as:
242
+ >
243
+ > Q1 is interested in all the orange animals. Q2 wants to hear everything about rabbits, and everything about lazy animals.
244
+ >
245
+ > A message with a routing key set to "`quick.orange.rabbit`" will be delivered to both queues. Message "`lazy.orange.elephant`" also will go to both of them. On the other hand "quick.orange.fox" will only go to the first queue, and "`lazy.brown.fox`" only to the second. "`lazy.pink.rabbit`" will be delivered to the second queue only once, even though it matches two bindings. "`quick.brown.fox`" doesn't match any binding so it will be discarded.
246
+
247
+
248
+
249
+ #### ElasticSearch
250
+ ElasticSearch is part of the so-called Elastic Stack. It is an open source database that offers powerful, fast querying of data. It is usually used with Logstash, also a tool of the Elastic Stack. Logstash can fetch or receive data from many different sources and can parse this data so it is in compliance with ElasticSearch data structures. At Chiligum, we create logs in JSON format and it is Logstash who converts it to Elasticsearch objects. Logstash can also work with RabbitMQ, creating queues and receiving data form them.
18
251
 
19
- Or install it yourself as:
252
+ #### Putting It All Together
253
+ By now, perhaps it has already become clear how RabbitMQ and ElasticSearch are being used for Chiligum's logging system. `ChiliLogger` will create logs and publish them to a topic exchange in `RabbitMQ` with the appropriate routing_keys. RabbitMQ will distribute these logs to the queues created by `Logstash` instances. The distribution of messages will be according to the queues' binding_keys. Logstash instances will keep polling the queues, fetching messages, parsing them to Elasticsearch objects and storing them in `Elasticsearch indexes`. An Elasticsearch index is analogous to a SQS table, it's basically a collection of stored data.
20
254
 
21
- $ gem install chili_logger
255
+ ![video showing Chiligum's logging system in action](assets/images/chiligum-logging-system.gif)
22
256
 
257
+ ## Installation
258
+
259
+ Add this line to your application's Gemfile:
260
+
261
+ ```ruby
262
+ gem 'chili_logger'
263
+ ```
264
+
265
+ #### Basic Initialization
266
+
267
+ Create a initializer file and configure ChiliLogger passing all the relevant information. It is recommended to deactivate ChiliLogger when running tests:
23
268
 
24
- Create a initializer file and configure ChiliLogger passing all the relevant informations:
25
269
  ```ruby
26
270
  # config/initializers/chili_logger.rb
27
271
  require 'chili_logger'
@@ -30,21 +274,20 @@ is_deactivated = Rails.env.test?
30
274
 
31
275
  ChiliLogger.instance.config({
32
276
  deactivated: is_deactivated,
33
- log_env: 'development',
34
- log_layer: 'creatives',
35
- server_url: ENV['SERVER_URL'],
36
- cloud_provider: ENV['CLOUD_PROVIDER'],
37
- msg_broker_name: :rabbitmq,
277
+ log_env: Rails.env, # sets the log's env attribute
278
+ log_layer: ENV['APPLICATION_NAME'], # sets the log's layer attribute
279
+ server_url: ENV['SERVER_URL'], # used for log's ops_metadata attribute
280
+ cloud_provider: ENV['CLOUD_PROVIDER'], # used for log's ops_metadata attribute
281
+ msg_broker_name: :rabbitmq, # ChiliLogger's Message Broker
38
282
  msg_broker_config: {
39
283
  user: ENV['RABBIT_USER'],
40
284
  password: ENV['RABBIT_PASSWORD'],
41
285
  ip: ENV['RABBIT_IP'],
42
286
  port: ENV['RABBIT_PORT'],
43
287
  exchange_name: ENV['RABBIT_EXCHANGE'],
44
- routing_key: ENV['RABBIT_ROUTING_KEY']
45
288
  },
46
- error_handler: :aws_sqs,
47
- error_handler_config: {
289
+ fallback_broker: :aws_sqs, # Chiligum's fallback option in case of Message Broker being down
290
+ fallback_broker_config: {
48
291
  region: ENV['CHILI_LOGGER_SQS_HANDLER_REGION'],
49
292
  access_key_id: ENV['CHILI_LOGGER_SQS_HANDLER_KEY_ID'],
50
293
  secret_access_key: ENV['CHILI_LOGGER_SQS_HANDLER_SECRET_KEY'],
@@ -52,31 +295,803 @@ ChiliLogger.instance.config({
52
295
  }
53
296
  })
54
297
  ```
298
+ The attributes `log_env` and `log_layer` set the fields ['env'](###environment) and ['layer'](###layer) in all logs that will be published by ChiliLogger.
299
+
300
+ When configuring ChiliLogger, you MUST set `msg_broker_name` and `msg_broker_config`. All logs are published to the configured message broker and, without it, ChiliLogger can't work. For the time being, only `:rabbitmq` is supported.
301
+
302
+ You ALSO MUST set `fallback_broker` and `fallback_broker_config`. This is the fallback option if the Message Broker is not available and, without it, ChiliLogger would break the application every time the Message Broker was down. For the time being, only `:aws_sqs` is supported.
303
+
304
+ Please note that if ChiliLogger tries to publish a log and both the configured Message Broker and the Error Handler are simultaneously down, the log will be discarded. This behavior makes sure logging problems never cause the app to break.
305
+
306
+ #### Overwriting RabbitMQ's routing_key
307
+ The [routing_key for all messages sent to rabbit will be created based on the description tag of the log](####description). But, sometimes, specially when testing, you may want to force all messages to be sent with a hardcoded routing_key. In those cases, you can add an optional field to the msg_broker_config: `routing_key_overwriter`.
308
+ ```ruby
309
+ ChiliLogger.instance.config({
310
+ ...
311
+ msg_broker_name: :rabbitmq,
312
+ msg_broker_config: {
313
+ user: ENV['RABBIT_USER'],
314
+ password: ENV['RABBIT_PASSWORD'],
315
+ ip: ENV['RABBIT_IP'],
316
+ port: ENV['RABBIT_PORT'],
317
+ exchange_name: ENV['RABBIT_EXCHANGE'],
318
+ routing_key_overwriter: 'a.hardcoded.routing.key'
319
+ },
320
+ ...
321
+ })
322
+ ```
323
+
324
+ ## Basic Usage
325
+
326
+ #### publish_instant_log params
327
+ The easiest way to use ChiliLogger is with the `publish_instant_log` method. It requires a hash with the following optional attributes:
328
+ ```ruby
329
+ {
330
+ desc: { # Hash
331
+ type: 'transaction', # String,
332
+ service: 'videos', # String
333
+ action: 'create', # String
334
+ },
335
+ user: {
336
+ cognito_id: 100, # String || Fixnum || Bignum -> ChiliLogger converts cognito_id fields with these types to Strings
337
+ email: 'example@chiligumvideos.com', # String
338
+ company_cognito_id: 88, # String || Fixnum || Bignum -> ChiliLogger converts company_cognito_id fields with these types to Strings
339
+ company_name: 'Chiligum', # String
340
+ ghost_user_cognito_id: 420, # String || Fixnum || Bignum -> ChiliLogger converts ghost_user_cognito_id with these types to Strings
341
+ },
342
+ main_content: { # Hash
343
+ modified_records: { # Hash
344
+ "#{tablename}": [ # Array of Hashes
345
+ { id: 42, title: 'Example video 1' },
346
+ { id: 42, title: 'Example video 2' },
347
+ ],
348
+ "#{another_tablename}": [ # Array of Hashes
349
+ { id: 42, title: 'Example banner 1' },
350
+ { id: 42, title: 'Example banner 2' },
351
+ ],
352
+ },
353
+ errors: [ # Array of Strings or Exception descendents -> ChiliLogger converts Exception descendents to Strings
354
+ 'first error',
355
+ StandardError.new('second error')
356
+ ]
357
+ },
358
+ }
359
+ ```
360
+ `desc` is used for setting the log's [type](###type), [service](###service), and [action](###action). env and layer were already set when ChiliLogger was [initialized and configured](##installation). Only `type`, `service`, and `action` are accepted.
361
+
362
+ `user` will set the user data. Only `cognito_id`, `email`, `company_cognito_id`, `company_name`, and `ghost_user_cognito_id` are accepted.
363
+
364
+ `main_content` will set the main information the log is concerned about. It only accepts `modified_records` or `errors`.
365
+
366
+ These fields must be set with the appropriate primitive types, as specified above. Not doing so [may create invalid logs](##read-before-contributing). Because of that, ChiliLogger tries its best to enforce that all fields in a log will have consistent primitive types.
367
+
368
+
369
+ #### publish_instant_log usage example
370
+ ```ruby
371
+ desc = { type: 'transaction', service: 'videos', action: 'create' }
372
+ user = {
373
+ cognito_id: 88,
374
+ email: 'exemple@test.com',
375
+ company_cognito_id: 75,
376
+ company_name: "Chiligum Creatives",
377
+ ghost_user_cognito_id: 55
378
+ },
379
+ main_content = {
380
+ modified_records: {
381
+ # notice we have a key 'videos' (the SQS tablename) pointing to an array of hashes
382
+ videos: [{ id: 42, title: 'Lateralus' }]
383
+ }
384
+ }
385
+
386
+ ChiliLogger.instance.publish_instant_log(desc: desc, user: user, main_content: main_content)
387
+ # publishes a json like this:
388
+ {
389
+ "env": "development",
390
+ "layer": "creatives",
391
+ "type": "transaction",
392
+ "service": "videos",
393
+ "action": "create",
394
+ "desc": "development.creatives.transaction.videos.create",
395
+ "user": {
396
+ "cognito_id": "88",
397
+ "email": 'exemple@test.com',
398
+ "company_cognito_id": "75",
399
+ "company_name": "Chiligum Creatives",
400
+ "ghost_user_cognito_id": "55"
401
+ },
402
+ "transaction": {
403
+ "modified_records": {
404
+ "videos": [{ "id": 42, "title": 'Lateralus' }]
405
+ },
406
+ "backtrace": [
407
+ "app/views/dashboard/controllers/videos_controller.rb:17 in block 'create'"
408
+ ]
409
+ },
410
+ "ops_metadata": {
411
+ "server_url": "https://app.chiligumvideos.com"
412
+ },
413
+ "timestamp": "2020-06-30T18:08:59-03:00",
414
+ }
415
+ ```
416
+
417
+ #### publish_instant_log default argument values
418
+ Passing `desc`, `agent`, and `main_content` is optional, since all of them have default values. This is so ChiliLogger is resiliant and doesn't break if any of these arguments is forgotten. But notice that their default values are not very descriptive and it results in low-quality, almost useless logs:
419
+
420
+ ```ruby
421
+ ChiliLogger.instance.publish_instant_log
422
+ # publishes a json like this:
423
+ {
424
+ "action": "not_specified",
425
+ "layer": "creatives",
426
+ "env": "development",
427
+ "type": "not_specified",
428
+ "service": "not_specified",
429
+ "desc": "development.creatives.not_specified.not_specified.not_specified",
430
+ "user": {
431
+ "cognito_id": 'not_specified',
432
+ "email": 'not_specified',
433
+ "company_cognito_id": 'not_specified',
434
+ "company_name": 'not_specified',
435
+ "ghost_user_cognito_id": 'not_specified'
436
+ },
437
+ "not_specified": {},
438
+ "backtrace": [
439
+ "app/views/dashboard/controllers/videos_controller.rb:17 in block 'create'"
440
+ ]
441
+ },
442
+ "ops_metadata": {
443
+ "server_url": "https://app.chiligumvideos.com"
444
+ },
445
+ "timestamp": "2020-06-30T18:08:59-03:00",
446
+ }
447
+ ```
448
+ #### ChiliLogger's Singleton pattern
449
+ Notice that ChiliLogger uses the Singleton pattern. So, to call its methods, you must first access the instance method. Calling ChiliLogger.new will return an error:
450
+ ```ruby
451
+ ChiliLogger.publish_instant_log(desc, agent, log)
452
+ # undefined method `publish_instant_log' for ChiliLogger:Class
453
+
454
+ ChiliLogger.new.publish_instant_log(desc, agent, log)
455
+ # NoMethodError: private method `new' called for ChiliLogger:Class
456
+
457
+ ChiliLogger.instance.publish_instant_log(desc, agent, log)
458
+ # works fine
459
+ ```
460
+
461
+
462
+ ## Advanced Usage
463
+ It will often happen that the information needed for a log is distributed in many parts of the application and can't be accessed in a single moment nor in a single place of the code base. For instance, you may want to set the log's agent with information about the current_user that's only available in the Controllers Layer. And you might want to add data to the log every time a DB record is modified, which is easier to do in the Models Layer. For these use cases, ChiliLogger's `publish_instant_log` will not be suitable and you should use more advanced features.
464
+
465
+ ChiliLogger takes advantage of Ruby's Thread class and allows you to start a log and keep adding information to it during the current thread's lifespan. This is particularly useful when logging requests to a controller, since these requests will run from beginning to end in a single thread.
466
+
467
+ Once a log is started, it can be accessed and modified by using ChiliLogger's `start_new_log` method and `current_log` accessor.
468
+
469
+ #### Advanced Code Example
470
+ ```ruby
471
+ # controllers/videos_controller.rb
472
+ class VideosController
473
+ def create
474
+ desc = { type: 'transaction', service: 'videos', action: 'create' }
475
+ agent = { user: current_user.as_json, company: current_user.as_json }
476
+ current_log = ChiliLogger.instance.start_new_log(desc, agent) # Step One
477
+
478
+ Video.create(title: 'Lateralus') # Step Two happens in the Video model
479
+
480
+ current_log.publish # Step Three
481
+ end
482
+ end
483
+
484
+ # models/video.rb
485
+ class Video < ApplicationRecord
486
+ after_create :add_modified_record_to_log
487
+
488
+ def add_modified_record_to_log
489
+ current_log = ChiliLogger.instance.current_log
490
+ modified_record = self.as_json
491
+
492
+ current_log.add_modified_record(self.class.table_name, modified_record) # Step Two
493
+ end
494
+ end
495
+
496
+ # resulting log (simplified)
497
+ {
498
+ "env": "development",
499
+ "layer": "creatives",
500
+ "type": "transaction",
501
+ "service": "videos",
502
+ "action": "create",
503
+ "desc": "development.creatives.transaction.videos.create",
504
+ "agent": {
505
+ "user": {
506
+ "id": 88,
507
+ "email": 'exemple@test.com'
508
+ },
509
+ "company": {
510
+ "id": 75,
511
+ "name": 'Chiligum'
512
+ }
513
+ },
514
+ "transaction": {
515
+ "modified_records": {
516
+ "videos": [{ "id": 42, "title": 'Lateralus' }]
517
+ },
518
+ "backtrace": [
519
+ "app/views/dashboard/controllers/videos_controller.rb:17 in block 'create'"
520
+ ]
521
+ },
522
+ "ops_metadata": {
523
+ "server_url": "https://app.chiligumvideos.com"
524
+ },
525
+ "timestamp": "2020-06-30T18:08:59-03:00",
526
+ }
527
+ ```
528
+
529
+ Notice that the log is started in VideosController#create with some initial info about the agent user and the log description; following, the same log has its main_content customized in Video.add_modified_record_to_log, by accessing ChiliLogger's `current_log` and calling its `add_modified_record` method. And, afterwards, the same log is finished and published with `current_log.publish`, again in VideosController#create.
530
+
531
+ Check the [Snippets For Quick Logging](##snippets-for-quick-logging) section to see how this functionality can be used to quickly set an all-embracing logging system for your application.
532
+
533
+ #### Error Logging
534
+ The controller code above could be improved by adding some logic to log unexpected errors:
535
+ ```ruby
536
+ # controllers/videos_controller.rb
537
+ class VideosController
538
+ def create
539
+ desc = { type: 'transaction', service: 'videos', action: 'create' }
540
+ agent = { user: current_user.as_json, company: current_user.as_json } # .as_json converts ActiveReacords into Hash
541
+ current_log = ChiliLogger.instance.start_new_log(desc, agent)
542
+
543
+ Video.create(title: 'Lateralus')
544
+
545
+ current_log.publish
546
+
547
+ rescue StandardError => error
548
+ #changes log type and publishes it
549
+ current_log.add_to_main_content({ error: error.as_json })
550
+ current_log.update_type('uncaught_error')
551
+ current_log.publish
552
+ end
553
+ end
554
+ ```
555
+ A [full list of methods](###customizing-a-started-log) for customizing a `current_log` is available
556
+
557
+ ### Customizing a Started Log
558
+ Once a log is started, its main attributes can be customized by accessing the `current_log` and calling one of its many customizing methods.
559
+ #### Accessing the current_log
560
+ ```ruby
561
+ current_log = ChiliLogger.instance.current_log
562
+ current_log.user # returns currently set user
563
+ current_log.update_user(cognito_id: '42', company_name: 'Chiligum') # updates user data
564
+ ```
565
+ #### user
566
+ returns log's currently set user
567
+
568
+ ```ruby
569
+ current_log.user
570
+ # { cognito_id: 'not_specified', email: 'not_specified', company_cognito_id: 'not_specified',
571
+ # company_name: 'not_specified', ghost_user_cognito_id: 'not_specified' }
572
+ ```
573
+
574
+ ---
575
+
576
+ #### update_user
577
+ updates the currently set user
578
+
579
+ ```ruby
580
+ current_log.user
581
+ # { cognito_id: 'not_specified', email: 'not_specified', company_cognito_id: 'not_specified',
582
+ # company_name: 'not_specified', ghost_user_cognito_id: 'not_specified' }
583
+
584
+ current_log.update_user({ email: 'new_email' } })
585
+ current_log.user
586
+ # { cognito_id: 'not_specified', email: 'new_email', company_cognito_id: 'not_specified',
587
+ # company_name: 'not_specified', ghost_user_cognito_id: 'not_specified' }
588
+ ```
589
+
590
+ ---
591
+
592
+ #### desc
593
+ returns currently set log's description. Notice that, for the time being, `env` and `layer` can't be overwritten, these description attributes can only be set during [ChiliLogger's configuration](##installation)
594
+
595
+ ```ruby
596
+ current_log.desc
597
+ # { type: 'not_specified', service: 'not_specified', action: 'not_specified' }
598
+ ```
599
+
600
+ ---
601
+
602
+ #### update_type
603
+ updates the type attribute currently set in the log's description
604
+
605
+ ```ruby
606
+ current_log.desc
607
+ # { type: 'not_specified', service: 'not_specified', action: 'not_specified' }
608
+
609
+ current_log.update_type('transaction')
610
+ current_log.desc
611
+ # { type: 'transaction', service: 'not_specified', action: 'not_specified' }
612
+ ```
613
+
614
+ ---
615
+
616
+ #### update_service
617
+ updates the service attribute currently set in the log's description
618
+
619
+ ```ruby
620
+ current_log.desc
621
+ # { type: 'not_specified', service: 'not_specified', action: 'not_specified' }
622
+
623
+ current_log.update_service('videos')
624
+ current_log.desc
625
+ # { type: 'not_specified', service: 'videos', action: 'not_specified' }
626
+ ```
627
+
628
+ ---
629
+
630
+ #### update_action
631
+ updates the action attribute currently set in the log's description
632
+
633
+ ```ruby
634
+ current_log.desc
635
+ # { type: 'not_specified', service: 'not_specified', action: 'not_specified' }
636
+
637
+ current_log.update_action('delete')
638
+ current_log.desc
639
+ # { type: 'not_specified', service: 'not_specified', action: 'delete' }
640
+ ```
641
+
642
+ ---
643
+
644
+ #### update_desc
645
+ updates the currently set log's description
646
+
647
+ ```ruby
648
+ current_log.desc
649
+ # { type: 'not_specified', service: 'not_specified', action: 'not_specified' }
650
+
651
+ current_log.update_desc({ type: 'uncaught_error', action: 'create' })
652
+ current_log.desc
653
+ # { type: 'uncaught_error', service: 'not_specified', action: 'create' }
654
+ ```
655
+
656
+ ---
657
+
658
+ #### main_content
659
+ returns the currently set main_content
660
+
661
+ ```ruby
662
+ current_log.main_content
663
+ # { modified_records: {}, errors: [] }
664
+
665
+ ```
666
+
667
+ ---
668
+
669
+ #### update_main_content
670
+ updates the currently set main_content
671
+
672
+ ```ruby
673
+ current_log.main_content
674
+ # { modified_records: {}, errors: [] }
675
+
676
+ new_content = {
677
+ modified_records: {
678
+ 'videos' => [{ title: 'Lateralus', id: 42 }]
679
+ }
680
+ }
681
+ current_log.update_main_content(new_content)
682
+ current_log.main_content
683
+ # {
684
+ # modified_records: {
685
+ # 'videos' => [{ title: 'Lateralus', id: 42 }]
686
+ # },
687
+ # errors: []
688
+ # }
689
+ ```
690
+
691
+ ---
692
+
693
+ #### add_to_main_content
694
+ merges hash with currently set main_content
695
+
696
+ ```ruby
697
+ current_log.main_content
698
+ # { modified_records: {}, errors: ['err1', 'err2'] }
699
+
700
+ current_log.add_to_main_content(errors: ['err50'])
701
+ current_log.main_content
702
+ # { modified_records: {}, errors: ['err1', 'err2', 'err50'] }
703
+ ```
704
+
705
+ ---
706
+
707
+ #### modified_records
708
+ returns the modified_records stored in the main_content
709
+
710
+ ```ruby
711
+ current_log.modified_records
712
+ # {}
713
+ ```
714
+
715
+ ---
716
+
717
+ #### overwrite_modified_records
718
+ overwrites the modified_records stored in the main_content
719
+
720
+ ```ruby
721
+ current_log.modified_records
722
+ # { tracks: [{ id: 87, title: 'Hips Dont Lie' }] }
723
+
724
+ current_log.overwrite_modified_records({ tracks: [{ id: 88, title: 'Lateralus' }] })
725
+ current_log.modified_records
726
+ # { tracks: [{ id: 88, title: 'Lateralus' }] }
727
+ ```
728
+
729
+ ---
730
+
731
+ #### add_modified_record
732
+ merges hash with currently set modified_records. Notice it receives two arguments: the `tablename` and the `record` itself;
733
+
734
+ ```ruby
735
+ current_log.modified_records
736
+ # { tracks: [{ id: 88, title: 'Lateralus' }] }
737
+
738
+ current_log.add_modified_records('tracks', { id: 89, title: "Hips Don't lie" })
739
+ current_log.modified_records
740
+ # {
741
+ # tracks: [{ id: 88, title: 'Lateralus' }, { id: 89, title: "Hips Don't lie" }],
742
+ # }
743
+
744
+ current_log.add_modified_records('videos', { id: 42, title: 'Life Of Brian' })
745
+ current_log.modified_records
746
+ # {
747
+ # tracks: [{ id: 88, title: 'Lateralus' }, { id: 89, title: "Hips Don't lie" }],
748
+ # videos: [{id: 42, title: 'Life Of Brian' }]
749
+ # }
750
+ ```
751
+
752
+ ---
753
+
754
+ #### errors
755
+ returns the errors stored in the main_content
756
+
757
+ ```ruby
758
+ current_log.errors
759
+ # []
760
+ ```
761
+
762
+ ---
763
+
764
+ #### overwrite_errors
765
+ overwrites the errors stored in the main_content
766
+
767
+ ```ruby
768
+ current_log.errors
769
+ # ['err1', 'err2', 'err3']
770
+
771
+ current_log.overwrite_errors(['err500'])
772
+ current_log.errors
773
+ # ['err500']
774
+ ```
775
+
776
+ ---
777
+
778
+ #### add_error
779
+ merges hash with currently set errors
780
+
781
+ ```ruby
782
+ current_log.errors
783
+ # ['err1', 'err2', 'err3']
784
+
785
+ current_log.add_error('err400')
786
+ current_log.errors
787
+ # ['err1', 'err2', 'err3', 'err400']}
788
+ ```
789
+
790
+ #### clear_log_info
791
+ sets agent, desc, and main_content to their default values.
792
+
793
+
794
+ ## Snippets for Quicker Logging
795
+ Following is a series of snippets for quickly setting up some basic logging functionality.
796
+ **Please notice that the snippets in this section use the [paper_trail gem](https://github.com/paper-trail-gem/paper_trail/blob/v9.2.0/README.md#1c-basic-usage) for improving logs. Its use is optional, but recommended.**
797
+
798
+ ### Papertrail Optional Use
799
+
800
+ > ChiliLogger works just fine without paper_trail. If you don't want to use paper_trail or if your application doesn't use ActiveRecords, you can skip the following code.
801
+ >
802
+ > Papertrail allows us to keep a history of changes made to ActiveRecords entities, which is great for logging. To install and setup `paper_trail`, add the following code:
803
+
804
+ // optional code if you want to use Papertrail to enrich logs
805
+ $ bundle add paper_trail
806
+ $ bundle install
807
+ $ bundle exec rails generate paper_trail:install
808
+ $ bundle exec rake db:migrate
809
+ and then:
810
+ ```ruby
811
+ # optional code if you want to use Papertrail to enrich logs
812
+
813
+ # models/application_record.rb
814
+ class ApplicationRecord < ActiveRecord::Base
815
+ self.abstract_class = true
816
+
817
+ has_paper_trail # add this line!
818
+ end
819
+ ```
820
+ If your controllers have a current_user, it usually won't be accessible in the models. When implementing logs, though, it is a behaviour you might be interested in, so you can log changes to the DB knowing who was the user responsible for that change. PaperTrail has a feature called `whodunnit`, which is [used precisely for this purpose](https://github.com/paper-trail-gem/paper_trail/blob/v10.3.1/README.md#setting-whodunnit-with-a-controller-callback).
821
+ ```ruby
822
+ # controllers/application_controller.rb
823
+ class ApplicationController < ActionController::Base
824
+ # sets papertrail Whodunit based on user defined in user_for_paper_trail
825
+ before_action :set_paper_trail_whodunnit
826
+
827
+ # customizes method implemented by PaperTrail and that sets PaperTrail.request.whodunit
828
+ # PaperTrail.request.whodunit will be available in all parts of application while a request is being processed
829
+ def user_for_paper_trail
830
+ current_user
831
+ end
832
+ end
833
+ ```
834
+
835
+ ### Logging Transactions in HTTP Requests
836
+ Transactions happen in two main layers of an application: the controllers handling the request and the models persisting data. If your application has an ApplicationController from which all other controllers inherit, and also an ApplicationRecord from which all models inherit, we can quickly set standardized logs for transactions and errors.
837
+
838
+ #### Controllers Logging Logic
839
+ Just add the following code to your ApplicationController:
840
+ ```ruby
841
+ # controllers/application_controller.rb
842
+ class ApplicationController < ActionController::Base
843
+ before_action :start_new_log
844
+ rescue_from StandardError, with: :publish_error_log
845
+ after_action :publish_log
846
+
847
+ def start_new_log
848
+ table_name = self.class.name.split('::').last.gsub('Controller', '').underscore
849
+ # action_name is available in Rails apps, Sinatra apps may need some other solution
850
+ log_action = action_name == 'destroy' ? 'delete' : action_name
851
+
852
+ desc ||= log_description('transaction', table_name, log_action)
853
+ user = log_user
854
+
855
+ ChiliLogger.instance.start_new_log(desc: desc, user: user)
856
+ end
857
+
858
+ # publishes transaction log with the main infos about the request
859
+ def publish_log
860
+ # only publish log if current log had modified_records added to it, so we don't clutter logs with index requests
861
+ return if ChiliLogger.instance.current_log.modified_records.empty?
862
+
863
+ ChiliLogger.instance.current_log.publish
864
+ end
865
+
866
+ # if unexpected errors happen, will change log type and publish it for debugging/audit
867
+ def publish_error_log(error)
868
+ error = [error.inspect, Rails.backtrace_cleaner.clean(error.backtrace)]
869
+ current_log = ChiliLogger.instance.current_log
870
+
871
+ current_log.add_error(error)
872
+ current_log.update_type('uncaught_error')
873
+ current_log.publish
874
+
875
+ raise error
876
+ end
877
+
878
+ private
879
+
880
+ def log_description(type, service, action)
881
+ { type: type, service: service, action: action }
882
+ end
883
+
884
+ # customize according to your app's schema
885
+ def log_user
886
+ return {} unless current_user
887
+
888
+ multi_user_record = MultiUser::User.find(current_user&.cognito_id)
889
+ log_user = {
890
+ email: current_user&.email,
891
+ cognito_id: multi_user_record&.id,
892
+ company_name: current_user&.company&.name,
893
+ company_cognito_id: multi_user_record&.organization&.id
894
+ }
895
+ log_user.merge!(ghost_user_cognito_id: admin_ghost_user&.cognito_id) # if app has admins that can log as other users
896
+ end
897
+ end
898
+ ```
899
+
900
+ Notice that `start_new_log` will set the log's service and action based on the controller name and the method being called. So if VideosController has its create method called, it would generate a log with service="videos" and action="create". Individual controllers and methods can be customized by using the [current_log accessors](###customizing-a-started-log).
901
+
902
+ For example, suppose we would like the GalleryFilesController to generate custom logs with service='gallery' and we would like GalleryFilesController#find_files to define action='filter'. We could do the following:
903
+ ```ruby
904
+ # app/controllers/gallery_files.rb
905
+ class GalleryFilesController < ApplicationController
906
+ before_action :overwrite_log_service
907
+
908
+ def find_files
909
+ ChiliLogger.instance.current_log.update_action('filter')
910
+ # method's usual code...
911
+ end
912
+
913
+ private
914
+
915
+ # customizes logs created by ApplicationController's start_new_log method
916
+ def overwrite_log_service
917
+ ChiliLogger.instance.current_log.update_service('gallery')
918
+ end
919
+ end
920
+ ```
921
+
922
+ #### Models Logging Logic
923
+ The code above implements automatic logs for requests made to controllers. We can further improve the logs being created by adding the DB records that were modified during the request:
924
+ ```ruby
925
+ class ApplicationRecord < ActiveRecord::Base
926
+ self.abstract_class = true
927
+
928
+ after_create -> { add_modified_record_to_log('create') }, on: :create
929
+ after_update -> { add_modified_record_to_log('update') }, on: :update
930
+ before_destroy -> { add_modified_record_to_log('destroy') }, on: :destroy
931
+
932
+ has_paper_trail # if you are using paper_trail
933
+
934
+ # enriches logs by automatically adding modified_records to them
935
+ def add_modified_record_to_log(action_verb, modified_record = self)
936
+ # only adds to log if record was created, changed or destroyed
937
+ return if !new_record? && changes.empty? && action_verb != 'destroy'
938
+
939
+ current_log = ChiliLogger.instance.current_log
940
+ current_log.update_type('transaction_error') unless modified_record.errors.messages.empty?
941
+ current_log.add_modified_record(self.class.table_name, modified_record.to_denormalized_hash)
942
+ end
943
+
944
+ # ChiliLogger requires modified_records to be hashes.
945
+ # This method converts ActiveRecords instances to hashes and adds some extra useful data
946
+ def to_denormalized_hash(record_hash = as_json)
947
+ record_hash[:last_changes] = versions.last.changeset if versions.last #if you are using paper_trail
948
+ record_hash[:errors] = errors
949
+
950
+ record_hash
951
+ end
952
+ end
953
+ ```
954
+
955
+ The code above will add all modified records to the transaction logs. We implement a `to_denormalized_hash` method, which converts the ActiveRecord isntance to a hash, as ChiliLogger requires. This method can be customized in individual models, to generate logs with even more information (for instance, denormalizing the main ActiveRecords relations):
956
+ ```ruby
957
+ # app/models/banner.rb
958
+ class Banner < ApplicationRecord
959
+ # customizes inherited ApplicationRecord's method, denormalizing the record's main relations
960
+ def to_denormalized_hash(record_hash = as_json)
961
+ relations = Banner.includes(:campaign)
962
+ .includes(template: %i[category template_collection])
963
+ .find(id)
964
+
965
+ record_hash[:campaign] = relations.campaign.as_json
966
+ record_hash[:template] = relations.template.as_json
967
+ record_hash[:template][:category] = relations.template.category.as_json
968
+ record_hash[:template][:template_collection] = relations.template.template_collection.as_json
969
+
970
+ super(record_hash) # calls ActiveRecords's to_denormalized_hash
971
+ end
972
+ end
973
+ ```
55
974
 
56
- When configuring ChiliLogger, you MUST set msg_broker_name and msg_broker_config. All logs are published to this message broker and, without it, ChiliLogger can't work. For the time being, only :rabbitmq is supported.
975
+ ### Logging Rake Tasks
976
+ When logging transactions made in tasks, we have three main concerns: logging uncaught errors in the task, logging transactions performed by the task and log whether the task is up and running, for monitoring.
57
977
 
58
- You ALSO MUST set error_handler and error_handler_config. The error handler is the fallback option if the message broker is not available and, without it, ChiliLogger will break the application every time the message broker is down. For the time being, only :aws_sqs is supported.
978
+ #### Logging Uncaught Task Errors
979
+ For this, we must create a standardized way of handling task errors. The best wayto do it is with a monkey-patch to rake, by creating the following file:
980
+ ```ruby
981
+ # config/initializers/task.rb
982
+ require 'rake/task'
983
+
984
+ module Rake
985
+ class Task
986
+ alias :original_execute :execute
59
987
 
60
- Please note that if ChiliLogger tries to publish a log and both the Message Broker and the Error Handler are simultaneously down, the log will be discarded. This behavior makes sure logging problems never cause the app to break.
988
+ # customizes the execute method of Rake::Task
989
+ def execute(args=nil)
990
+ begin
991
+ # start storing default log infos for rake tasks before they even begin
992
+ log_desc = { log_type: 'error', service: 'automated_task', action: 'uncaught_error' }
993
+ log_user = { email: 'automated_task', cognito_id: 'automated_task' }
994
+ ChiliLogger.instance.start_new_log(desc: log_desc, user: log_user)
61
995
 
996
+ original_execute(args)
62
997
 
998
+ rescue StandardError => error
999
+ current_log = ChiliLogger.instance.current_log
63
1000
 
1001
+ current_log.add_error(error.inspect)
1002
+ current_log.update_type('uncaught_error')
1003
+ current_log.publish
64
1004
 
65
- ## Usage
1005
+ raise error
1006
+ end
1007
+ end
1008
+ end
1009
+ end
1010
+ ```
1011
+
1012
+ #### Logging Transactions in Tasks
1013
+ Unfortunately, for the time being, we still haven't found a practical way to log all Task transactions with minimal setup. The way most of our tasks are implemented - as infinite loops - requires us to add logging logic to each and every task, by doing so:
1014
+ ```ruby
1015
+ # lib/tasks/example_task.rake
1016
+ namespace :excel do
1017
+ task validation: :environment do
1018
+ loop do
1019
+ # start storing infos for validation log
1020
+ transaction_desc = { log_type: 'transaction', service: 'excel', action: 'validation' }
1021
+ log_user = { email: 'automated_task', cognito_id: 'automated_task'
1022
+ current_log = ChiliLogger.instance.start_new_log(desc: log_desc, user: log_user)
66
1023
 
67
- TODO: Write usage instructions here
1024
+ usual task code...
1025
+
1026
+ # only publish log if current log had modified_records added to it - so we don't clutter DB with meaningless logs
1027
+ current_log.publish if current_log.modified_records
68
1028
 
69
- ## Development
1029
+ sleep 5
1030
+ end
1031
+ end
1032
+ ```
1033
+ Until we come up with a better solution, this will have to be done to each and every task of interest. Notice that the snippet above assumes you have [edited the models to enrich logs with modified_records](####models-logging-logic).
1034
+
1035
+ #### Logging Heartbeats in Tasks
1036
+ Besides monitoring transactions and unexpected errors, it is also important to monitor whether all main parts of an application are up and running. For servers, we do that by pinging specific endpoints to see if the server is up and responding. Tasks pose a different problem, since they have no endpoint we can ping. Besides, a task maybe up and with a 'running' status, but being stuck all the same and not processing new data. For that reason, we use ChiliLogger to monitor tasks' healht.
1037
+
1038
+ Unfortunately, due to the way most of our tasks are implemented - as infinite loops - we must add logging logic to each and every task, by doing so:
1039
+ ```ruby
1040
+ # lib/tasks/example_task.rake
1041
+ namespace :excel do
1042
+ task validation: :environment do
1043
+ loop do
1044
+ heartbeat_desc = { log_type: 'monitoring', service: 'excel', action: 'validation' }
1045
+ log_user = { email: 'automated_task', cognito_id: 'automated_task'
1046
+ ChiliLogger.instance.publish_instant_log(desc: heartbeat_desc, user: log_user)
70
1047
 
71
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
1048
+ usual task code...
72
1049
 
73
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
1050
+ sleep 5
1051
+ end
1052
+ end
74
1053
 
75
- ## Contributing
1054
+ ```
76
1055
 
77
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/chili_logger.
1056
+ ## Coverage
1057
+ ChiliLogger keeps a coverage report, showing what kinds of logs are being created. This can be usefull to see whether all important points of an application are being satisfactorily monitored and also to have an overview of how these logs description tags are looking like. This coverage report can be found in `app_root/log/chili-logger-coverage.yml`. It is an YAML file with 4 dimensions:
1058
+ - first dimension: type;
1059
+ - second dimension: service;
1060
+ - third dimension: action;
1061
+ - fourth dimension: an array with the backtrace of the last created log to a given type, service and action combination;
78
1062
 
1063
+ ```yaml
1064
+ transaction: #log's type
1065
+ videos: # log's service
1066
+ create: # log's action
1067
+ - "backtrace_path_1" # backtrace for transaction.videos.create
1068
+ - "backtrace_path_2"
1069
+ - "backtrace_path_3"
1070
+ update: # log's action
1071
+ - "backtrace_path_1" # backtrace for transaction.videos.update
1072
+ - "backtrace_path_2"
1073
+ - "backtrace_path_3"
1074
+ banners:
1075
+ create: # log's action
1076
+ - "backtrace_path_1" # backtrace for transaction.banners.create
1077
+ - "backtrace_path_2"
1078
+ - "backtrace_path_3"
1079
+ accept: # log's action
1080
+ - "backtrace_path_1" # backtrace for transaction.banners.accept
1081
+ - "backtrace_path_2"
1082
+ - "backtrace_path_3"
79
1083
 
80
- ## License
1084
+ uncaught_error: #log's type
1085
+ gallery: # log's service
1086
+ index: # log's action
1087
+ - "backtrace_path_1" # backtrace for uncaught_error.gallery_index
1088
+ - "backtrace_path_2"
1089
+ - "backtrace_path_3"
81
1090
 
82
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
1091
+ monitoring: #log's type
1092
+ campaigns: # log's service
1093
+ validate_sheet: # log's action
1094
+ - "backtrace_path_1" # backtrace for monitoring.campaigns.validate_sheet
1095
+ - "backtrace_path_2"
1096
+ - "backtrace_path_3"
1097
+ ```