/src/lib/log/conf/log_internal_conf.e
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.