PageRenderTime 91ms CodeModel.GetById 70ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 1ms

/src/lib/log/conf/log_internal_conf.e

http://github.com/tybor/Liberty
Specman e | 654 lines | 578 code | 43 blank | 33 comment | 49 complexity | 41d04ad48a16b18e838e370c60a0831e MD5 | raw file
  1-- This file is part of a Liberty Eiffel library.
  2-- See the full copyright at the end.
  3--
  4class LOG_INTERNAL_CONF
  5   --
  6   -- The internal logging configuration manager
  7   --
  8
  9inherit
 10   EIFFEL_NON_TERMINAL_NODE_IMPL_VISITOR
 11      undefine
 12         is_equal
 13      end
 14   EIFFEL_TERMINAL_NODE_IMPL_VISITOR
 15      undefine
 16         is_equal
 17      end
 18   EIFFEL_LIST_NODE_IMPL_VISITOR
 19      undefine
 20         is_equal
 21      end
 22
 23insert
 24   SINGLETON
 25
 26create {LOG_CONFIGURATION}
 27   make
 28
 29feature {}
 30   root: LOGGER
 31   loggers: HASHED_DICTIONARY[LOGGER, FIXED_STRING]
 32   outputs: HASHED_DICTIONARY[LOG_OUTPUT, FIXED_STRING]
 33
 34feature {EIFFEL_NON_TERMINAL_NODE_IMPL}
 35   visit_eiffel_non_terminal_node_impl (node: EIFFEL_NON_TERMINAL_NODE_IMPL)
 36      do
 37         pass.call([node])
 38      end
 39
 40feature {}
 41   do_pass_1 (on_error: PROCEDURE[TUPLE[STRING]]; path_resolver: FUNCTION[TUPLE[STRING], STRING]; node: EIFFEL_NON_TERMINAL_NODE_IMPL)
 42         -- Create outputs and loggers
 43      require
 44         on_error /= Void
 45      local
 46         output_name: FIXED_STRING; output: LOG_OUTPUT
 47         logger_name: FIXED_STRING; logger: LOGGER
 48         term: EIFFEL_TERMINAL_NODE
 49         num: TYPED_EIFFEL_IMAGE[INTEGER_64]; rotation: INTEGER_64
 50         rotation_condition: PREDICATE[TUPLE[FILE_STREAM]]
 51         file_path: STRING
 52         file_options_index: INTEGER
 53      do
 54         inspect
 55            node.name
 56         when "Configuration" then
 57            node.node_at(3).accept(Current)
 58            node.node_at(4).accept(Current)
 59            node.node_at(2).accept(Current)
 60            root := loggers.fast_reference_at(last_class_name.intern)
 61            if root = Void then
 62               on_error.call(["Unknown root logger: " + last_class_name])
 63            end
 64         when "Root" then
 65            node.node_at(1).accept(Current)
 66         when "Outputs" then
 67            node.node_at(1).accept(Current)
 68         when "Output" then
 69            node.node_at(0).accept(Current)
 70            output_name := last_entity_name.intern
 71            output := outputs.fast_reference_at(output_name)
 72            if output = Void then
 73               last_format := Void
 74               inspect
 75                  node.node_at(2).name
 76               when "KW file" then
 77                  node.node_at(3).accept(Current)
 78                  -- TODO: to check when the "url" construct is implemented
 79                  file_path := path_resolver.item([last_string])
 80                  create file_options.file(output_name, file_path)
 81                  file_options_index := 4
 82               when "KW console" then
 83                  create file_options.console(output_name)
 84                  file_options_index := 3
 85               end
 86               if file_options.is_connected then
 87                  node.node_at(file_options_index).accept(Current)
 88                  create output.make(file_options.retriever, output_name)
 89                  outputs.put(output, output_name)
 90               else
 91                  on_error.call([output_name + ": could not connect to " + file_path])
 92               end
 93               if last_format /= Void then
 94                  output.set_format(last_format)
 95               end
 96            else
 97               on_error.call(["Duplicate output name: " + output_name])
 98            end
 99         when "File_Options" then
100            if not node.is_empty then
101               node.node_at(0).accept(Current)
102            end
103         when "File_Option" then
104            inspect
105               node.name_at(0)
106            when "KW rotated" then
107               node.node_at(3).accept(Current)
108               node.node_at(2).accept(Current)
109            when "KW zipped" then
110               node.node_at(2).accept(Current)
111               file_options.zipped(last_string.intern, on_error)
112            when "KW format" then
113               node.node_at(1).accept(Current)
114               last_format := last_string.intern
115            else
116               node.node_at(0).accept(Current)
117            end
118         when "Rotation" then
119            if node.count = 1 then
120               rotation := 1
121            else
122               term ::= node.node_at(1)
123               if num ?:= term.image then
124                  num ::= term.image
125                  rotation := num.decoded
126                  if rotation < 1 then
127                     on_error.call(["Bad retention value: " + term.image.image])
128                     rotation := 1
129                  end
130               else
131                  on_error.call(["Bad retention value: " + term.image.image])
132                  rotation := 1
133               end
134            end
135            inspect
136               node.name_at(node.upper)
137            when "KW day",      "KW days"      then
138               rotation_condition := agent each_days(?, rotation)
139            when "KW week",     "KW weeks"     then
140               rotation_condition := agent each_weeks(?, rotation, 0)
141            when "KW year",     "KW years"     then
142               rotation_condition := agent each_years(?, rotation)
143            when "KW month",    "KW months"    then
144               rotation_condition := agent each_months(?, rotation)
145            when "KW hour",     "KW hours"     then
146               rotation_condition := agent each_hours(?, rotation)
147            when "KW minute",   "KW minutes"   then
148               rotation_condition := agent each_minutes(?, rotation)
149            when "KW byte",     "KW bytes"     then
150               rotation_condition := agent each_bytes(?, rotation)
151            when "KW kilobyte", "KW kilobytes" then
152               rotation_condition := agent each_bytes(?, rotation * 1024)
153            when "KW megabyte", "KW megabytes" then
154               rotation_condition := agent each_bytes(?, rotation * 1024 * 1024)
155            when "KW gigabyte", "KW gigabytes" then
156               rotation_condition := agent each_bytes(?, rotation * 1024 * 1024 * 1024)
157            end
158            file_options.rotated(rotation_condition, last_retention, on_error)
159         when "Retention" then
160            if node.is_empty then
161               last_retention := -1
162            else
163               term ::= node.node_at(1)
164               if num ?:= term.image then
165                  num ::= term.image
166                  last_retention := num.decoded
167                  if last_retention < 0 then
168                     on_error.call(["Bad retention value: " + term.image.image])
169                     last_retention := -1
170                  end
171               else
172                  on_error.call(["Bad retention value: " + term.image.image])
173                  last_retention := -1
174               end
175            end
176         when "Loggers" then
177            node.node_at(1).accept(Current)
178         when "Logger" then
179            node.node_at(0).accept(Current)
180            logger_name := last_class_name.intern
181            last_entity_name := Void
182            node.node_at(3).accept(Current)
183            if last_entity_name = Void then
184               output := default_output
185            else
186               output_name := last_entity_name.intern
187               output := outputs.fast_reference_at(output_name)
188               if output = Void then
189                  on_error.call(["Unknown output: " + output_name])
190               end
191            end
192            create logger.make(output, logger_name, generation_id)
193            loggers.put(logger, logger_name)
194         when "Logger_Output" then
195            if not node.is_empty then
196               node.node_at(1).accept(Current)
197            end
198         when "Level" then
199            if not node.is_empty then
200               node.node_at(1).accept(Current)
201            end
202         end
203      end
204
205   do_pass_2 (on_error: PROCEDURE[TUPLE[STRING]]; node: EIFFEL_NON_TERMINAL_NODE_IMPL)
206         -- Attach loggers' parents
207      require
208         on_error /= Void
209      local
210         logger_name: FIXED_STRING; logger, parent: LOGGER
211      do
212         inspect
213            node.name
214         when "Configuration" then
215            node.node_at(4).accept(Current)
216         when "Loggers" then
217            node.node_at(1).accept(Current)
218         when "Logger" then
219            node.node_at(0).accept(Current)
220            logger_name := last_class_name.intern
221            current_logger := loggers.fast_reference_at(logger_name)
222            last_level := Void
223            last_format := Void
224            check
225               current_logger /= Void
226            end
227            node.node_at(2).accept(Current)
228            node.node_at(4).accept(Current)
229            if last_level /= Void then
230               current_logger.set_level(last_level)
231            end
232            current_logger := Void
233         when "Level" then
234            if not node.is_empty then
235               node.node_at(1).accept(Current)
236            end
237         when "Parent" then
238            check
239               current_logger /= Void
240            end
241            last_level := levels.trace
242            if not node.is_empty then
243               if logger = root then
244                  on_error.call([once "The root logger cannot have a parent"])
245               else
246                  last_class_name := Void
247                  node.node_at(1).accept(Current)
248                  if last_class_name /= Void then
249                     parent := loggers.fast_reference_at(last_class_name.intern)
250                     if parent /= Void then
251                        current_logger.set_parent(parent)
252                        last_level := parent.level
253                     else
254                        on_error.call(["Unknown logger " + last_class_name + " for logger " + logger_name])
255                     end
256                  else
257                     logger.set_parent(root)
258                  end
259               end
260            end
261         end
262      end
263
264   file_options: LOG_FILE_OPTIONS
265
266   each_days (stream: FILE_STREAM; number: INTEGER_64): BOOLEAN
267      require
268         number > 0
269      local
270         now, last_change: TIME; elapsed: REAL
271      do
272         now.update
273         last_change := ft.last_change_of(stream.path)
274         if now < last_change then
275            Result := True
276         elseif now.year /= last_change.year or else now.year_day /= last_change.year_day then
277            elapsed := last_change.elapsed_seconds(now)
278            Result := elapsed > ((number - 1) * 86400).force_to_real_64
279         end
280      end
281
282   each_weeks (stream: FILE_STREAM; number: INTEGER_64; week_day: INTEGER): BOOLEAN
283      require
284         number > 0
285         week_day.in_range(0, 6)
286      local
287         now, last_change: TIME; elapsed: REAL
288      do
289         now.update
290         last_change := ft.last_change_of(stream.path)
291         if now < last_change then
292            Result := True
293         elseif now.year /= last_change.year or else now.year_day /= last_change.year_day then
294            -- TODO: not sure of that algorithm (thought power exhausted)
295            elapsed := last_change.elapsed_seconds(now)
296            if last_change.week_day = week_day then
297               Result := elapsed > ((number - 1) * 86400 * 7).force_to_real_64
298            else
299               Result := elapsed > (number * 86400 * 7).force_to_real_64
300            end
301         end
302      end
303
304   each_months (stream: FILE_STREAM; number: INTEGER_64): BOOLEAN
305      require
306         number > 0
307      local
308         now, last_change: TIME
309      do
310         now.update
311         last_change := ft.last_change_of(stream.path)
312         if now < last_change then
313            Result := True
314         else
315            Result := (now.year - last_change.year) * 12 + (now.month - last_change.month) >= number
316         end
317      end
318
319   each_years (stream: FILE_STREAM; number: INTEGER_64): BOOLEAN
320      require
321         number > 0
322      local
323         now, last_change: TIME
324      do
325         now.update
326         last_change := ft.last_change_of(stream.path)
327         if now < last_change then
328            Result := True
329         else
330            Result := (now.year - last_change.year) >= number
331         end
332      end
333
334   each_hours (stream: FILE_STREAM; number: INTEGER_64): BOOLEAN
335      require
336         number > 0
337      local
338         now, last_change: TIME; elapsed: REAL
339      do
340         now.update
341         last_change := ft.last_change_of(stream.path)
342         if now < last_change then
343            Result := True
344         elseif now.year /= last_change.year or else now.year_day /= last_change.year_day or else now.hour /= last_change.hour then
345            elapsed := last_change.elapsed_seconds(now)
346            Result := elapsed > ((number - 1) * 3600).force_to_real_64
347         end
348      end
349
350   each_minutes (stream: FILE_STREAM; number: INTEGER_64): BOOLEAN
351      require
352         number > 0
353      local
354         now, last_change: TIME; elapsed: REAL
355      do
356         now.update
357         last_change := ft.last_change_of(stream.path)
358         if now < last_change then
359            Result := True
360         elseif now.year /= last_change.year or else now.year_day /= last_change.year_day or else now.hour /= last_change.hour or else now.minute /= last_change.minute then
361            elapsed := last_change.elapsed_seconds(now)
362            Result := elapsed > ((number - 1) * 60).force_to_real_64
363         end
364      end
365
366   each_bytes (stream: FILE_STREAM; number: INTEGER_64): BOOLEAN
367      require
368         number > 0
369      do
370         Result := ft.size_of(stream.path) >= number
371      end
372
373   ft: FILE_TOOLS
374
375feature {EIFFEL_TERMINAL_NODE_IMPL}
376   visit_eiffel_terminal_node_impl (node: EIFFEL_TERMINAL_NODE_IMPL)
377      local
378         string: TYPED_EIFFEL_IMAGE[STRING]
379      do
380         inspect
381            node.name
382         when "KW class name" then
383            last_class_name := node.image.image
384         when "KW entity name" then
385            last_entity_name := node.image.image
386         when "KW string" then
387            string ::= node.image
388            last_string := string.decoded
389         when "KW number" then
390            last_number := node.image
391         when "KW error" then
392            last_level := levels.error
393         when "KW warning" then
394            last_level := levels.warning
395         when "KW info" then
396            last_level := levels.info
397         when "KW trace" then
398            last_level := levels.trace
399         else
400            -- nothing (syntax sugar)
401         end
402      end
403
404   fatal_error (message: STRING)
405      do
406         std_error.put_line(message)
407         die_with_code(1)
408      end
409
410   default_path_resolver: FUNCTION[TUPLE[STRING], STRING]
411      once
412         Result := agent resolve_path(?)
413      end
414
415   resolve_path (a_path: STRING): STRING
416      do
417         Result := a_path
418      end
419
420feature {EIFFEL_LIST_NODE_IMPL}
421   visit_eiffel_list_node_impl (node: EIFFEL_LIST_NODE_IMPL)
422      local
423         i: INTEGER
424      do
425         from
426            i := node.lower
427         until
428            i > node.upper
429         loop
430            node.item(i).accept(Current)
431            i := i + 1
432         end
433      end
434
435feature {LOG_CONFIGURATION}
436   load (a_stream: INPUT_STREAM; when_error: PROCEDURE[TUPLE[STRING]]; a_path_resolver: FUNCTION[TUPLE[STRING], STRING]; a_load_completion: PROCEDURE[TUPLE])
437      local
438         load_item: TUPLE[INPUT_STREAM, PROCEDURE[TUPLE[STRING]], FUNCTION[TUPLE[STRING], STRING], PROCEDURE[TUPLE]]
439      do
440         if loading then
441            load_queue.add_last([a_stream, when_error, a_path_resolver, a_load_completion])
442         else
443            load_default
444            loading := True
445            from
446               load_(a_stream, when_error, a_path_resolver, a_load_completion)
447            until
448               load_queue.is_empty
449            loop
450               load_item := load_queue.first
451               load_queue.remove_first
452               load_(load_item.first, load_item.second, load_item.third, load_item.fourth)
453            end
454            loading := False
455         end
456      end
457
458   conf_logger (a_tag: FIXED_STRING): LOGGER
459      require
460         a_tag.intern = a_tag
461      local
462         i: INTEGER; parent: LOGGER; parent_tag: FIXED_STRING
463      do
464         load_default
465         Result := loggers.fast_reference_at(a_tag)
466         if Result = Void then
467            i := a_tag.first_index_of('[')
468            if a_tag.valid_index(i) then
469               parent_tag := a_tag.substring(a_tag.lower, i - 1).intern
470               parent := loggers.fast_reference_at(parent_tag)
471               if parent = Void then
472                  create parent.make(root.output, parent_tag, generation_id)
473                  parent.set_parent(root)
474                  loggers.put(parent, parent_tag)
475               end
476            else
477               parent := root
478            end
479            check
480               parent /= Void
481            end
482            create Result.make(parent.output, a_tag, generation_id)
483            Result.set_parent(parent)
484            loggers.put(Result, a_tag)
485         end
486      ensure
487         Result.tag = a_tag
488      end
489
490   generation_id: INTEGER
491      do
492         Result := generations.item
493      end
494
495feature {}
496   load_queue: RING_ARRAY[TUPLE[INPUT_STREAM, PROCEDURE[TUPLE[STRING]], FUNCTION[TUPLE[STRING], STRING], PROCEDURE[TUPLE]]]
497   loading: BOOLEAN
498
499   load_ (a_stream: INPUT_STREAM; when_error: PROCEDURE[TUPLE[STRING]]; a_path_resolver: FUNCTION[TUPLE[STRING], STRING]; a_load_completion: PROCEDURE[TUPLE])
500      require
501         a_stream.is_connected
502      local
503         conf: STRING
504         on_error: like when_error
505         path_resolver: FUNCTION[TUPLE[STRING], STRING]
506      do
507         if when_error = Void then
508            on_error := agent fatal_error(?)
509         else
510            on_error := when_error
511         end
512
513         if a_path_resolver = Void then
514            path_resolver := default_path_resolver
515         else
516            path_resolver := a_path_resolver
517         end
518
519         conf := once ""
520         conf.clear_count
521         from
522            a_stream.read_line
523         until
524            a_stream.end_of_input
525         loop
526            conf.append(a_stream.last_string)
527            conf.extend('%N')
528            a_stream.read_line
529         end
530         conf.append(a_stream.last_string)
531         parser_buffer.initialize_with(conf)
532
533         grammar.reset
534         if parser.eval(parser_buffer, grammar.table, once "Configuration") then
535            if parser.error /= Void then
536               on_error.call([parser.error.message])
537            else
538               generations.next
539               loggers.clear_count
540               outputs.clear_count
541
542               pass := agent do_pass_1(on_error, path_resolver, ?)
543               grammar.root_node.accept(Current)
544               pass := agent do_pass_2(on_error, ?)
545               grammar.root_node.accept(Current)
546            end
547         else
548            if when_error /= Void then
549               when_error.call(["Truncated log configuration file"])
550            else
551               std_error.put_line("Truncated log configuration file")
552               die_with_code(1)
553            end
554         end
555
556         if a_load_completion /= Void then
557            a_load_completion.call([])
558         end
559      end
560
561feature {}
562   make
563      do
564         create loggers.make
565         create outputs.make
566         create generations
567         create load_queue.make(1, 0)
568      end
569
570   load_default
571      local
572         in: TEXT_FILE_READ
573         o: LOG_OUTPUT
574         root0: like root
575      once
576         -- This very basic initialization ensures that a root always exists, which is useful while parsing
577         -- the log file (the parsing engine itself uses the logging framework...)
578         create o.make(agent pass_through(std_output), "root".intern)
579         create root0.make(o, "root".intern, generation_id)
580         root0.set_level(levels.info)
581         root := root0
582
583         if ft.file_exists("log.rc") then
584            create in.connect_to("log.rc")
585            if in.is_connected then
586               load(in, Void, Void, agent (a_in: TEXT_FILE_READ) do a_in.disconnect end(in))
587            end
588            if root = Void then
589               std_error.put_line(once "Could not initialize the logging framework.%NPlease check your log.rc file or explicitly call LOG_CONFIGURATION.load")
590               die_with_code(1)
591            end
592         end
593      end
594
595   last_class_name: STRING
596   last_entity_name: STRING
597   last_string: STRING
598   last_number: EIFFEL_IMAGE
599   last_level: LOG_LEVEL
600   last_format: FIXED_STRING
601   last_retention: INTEGER_64
602   current_logger: LOGGER
603   pass: PROCEDURE[TUPLE[EIFFEL_NON_TERMINAL_NODE_IMPL]]
604
605   levels: LOG_LEVELS
606
607   grammar: LOG_GRAMMAR
608      once
609         create Result.make(create {LOG_NODE_FACTORY}.make)
610      end
611
612   parser_buffer: MINI_PARSER_BUFFER
613      once
614         create Result
615      end
616
617   parser: DESCENDING_PARSER
618      once
619         create Result.make
620      end
621
622   default_output: LOG_OUTPUT
623      once
624         create Result.make(agent pass_through(std_output), "*default*".intern)
625      end
626
627   generations: COUNTER
628
629   pass_through (a_output: OUTPUT_STREAM): OUTPUT_STREAM
630      do
631         Result := a_output
632      end
633
634end -- class LOG_INTERNAL_CONF
635--
636-- Copyright (C) 2009-2017: by all the people cited in the AUTHORS file.
637--
638-- Permission is hereby granted, free of charge, to any person obtaining a copy
639-- of this software and associated documentation files (the "Software"), to deal
640-- in the Software without restriction, including without limitation the rights
641-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
642-- copies of the Software, and to permit persons to whom the Software is
643-- furnished to do so, subject to the following conditions:
644--
645-- The above copyright notice and this permission notice shall be included in
646-- all copies or substantial portions of the Software.
647--
648-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
649-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
650-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
651-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
652-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
653-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
654-- THE SOFTWARE.