├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── TODO ├── dialyzer.ignore-warnings ├── include └── lager.hrl ├── priv └── edoc.css ├── rebar ├── rebar.config ├── src ├── error_logger_lager_h.erl ├── lager.app.src ├── lager.erl ├── lager_app.erl ├── lager_backend_throttle.erl ├── lager_common_test_backend.erl ├── lager_config.erl ├── lager_console_backend.erl ├── lager_crash_log.erl ├── lager_default_formatter.erl ├── lager_file_backend.erl ├── lager_format.erl ├── lager_handler_watcher.erl ├── lager_handler_watcher_sup.erl ├── lager_manager_killer.erl ├── lager_msg.erl ├── lager_stdlib.erl ├── lager_sup.erl ├── lager_transform.erl ├── lager_trunc_io.erl └── lager_util.erl ├── test ├── compress_pr_record_test.erl ├── crash.erl ├── lager_app_tests.erl ├── lager_crash_backend.erl ├── lager_manager_killer_test.erl ├── lager_rotate.erl ├── lager_slow_backend.erl ├── lager_test_backend.erl ├── pr_nested_record_test.erl ├── pr_stacktrace_test.erl ├── special_process.erl ├── sync_error_logger.erl ├── trunc_io_eqc.erl └── zzzz_gh280_crash.erl └── tools.mk /.gitignore: -------------------------------------------------------------------------------- 1 | .eunit 2 | *.beam 3 | ebin 4 | doc 5 | *.swp 6 | erl_crash.dump 7 | .project 8 | log 9 | deps 10 | .local_dialyzer_plt 11 | dialyzer_unhandled_warnings 12 | dialyzer_warnings 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: erlang 3 | notifications: 4 | email: eng@basho.com 5 | otp_release: 6 | - 18.2.1 7 | - 17.5 8 | - R16B03-1 9 | - R15B03 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all compile deps clean distclean test check_plt build_plt dialyzer \ 2 | cleanplt 3 | 4 | all: deps compile 5 | 6 | compile: deps 7 | ./rebar compile 8 | 9 | deps: 10 | test -d deps || ./rebar get-deps 11 | 12 | clean: 13 | ./rebar clean 14 | 15 | distclean: clean 16 | ./rebar delete-deps 17 | 18 | DIALYZER_APPS = kernel stdlib erts sasl eunit syntax_tools compiler crypto \ 19 | common_test 20 | 21 | include tools.mk 22 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Time based log rotation 2 | Syslog backends (local & remote) 3 | debug_module & debug_pid 4 | -------------------------------------------------------------------------------- /dialyzer.ignore-warnings: -------------------------------------------------------------------------------- 1 | lager_trunc_io.erl:283: Call to missing or unexported function erlang:is_map/1 2 | lager_trunc_io.erl:335: Call to missing or unexported function erlang:map_size/1 3 | Unknown functions: 4 | lager_default_tracer:info/1 5 | maps:to_list/1 6 | -------------------------------------------------------------------------------- /include/lager.hrl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. 2 | %% 3 | %% This file is provided to you under the Apache License, 4 | %% Version 2.0 (the "License"); you may not use this file 5 | %% except in compliance with the License. You may obtain 6 | %% a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, 11 | %% software distributed under the License is distributed on an 12 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | %% KIND, either express or implied. See the License for the 14 | %% specific language governing permissions and limitations 15 | %% under the License. 16 | 17 | 18 | -define(DEFAULT_TRUNCATION, 4096). 19 | -define(DEFAULT_TRACER, lager_default_tracer). 20 | -define(DEFAULT_SINK, lager_event). 21 | -define(ERROR_LOGGER_SINK, error_logger_lager_event). 22 | 23 | 24 | -define(LEVELS, 25 | [debug, info, notice, warning, error, critical, alert, emergency, none]). 26 | 27 | %% Use of these "functions" means that the argument list will not be 28 | %% truncated for safety 29 | -define(LEVELS_UNSAFE, 30 | [{debug_unsafe, debug}, {info_unsafe, info}, {notice_unsafe, notice}, {warning_unsafe, warning}, {error_unsafe, error}, {critical_unsafe, critical}, {alert_unsafe, alert}, {emergency_unsafe, emergency}]). 31 | 32 | -define(DEBUG, 128). 33 | -define(INFO, 64). 34 | -define(NOTICE, 32). 35 | -define(WARNING, 16). 36 | -define(ERROR, 8). 37 | -define(CRITICAL, 4). 38 | -define(ALERT, 2). 39 | -define(EMERGENCY, 1). 40 | -define(LOG_NONE, 0). 41 | 42 | -define(LEVEL2NUM(Level), 43 | case Level of 44 | debug -> ?DEBUG; 45 | info -> ?INFO; 46 | notice -> ?NOTICE; 47 | warning -> ?WARNING; 48 | error -> ?ERROR; 49 | critical -> ?CRITICAL; 50 | alert -> ?ALERT; 51 | emergency -> ?EMERGENCY 52 | end). 53 | 54 | -define(NUM2LEVEL(Num), 55 | case Num of 56 | ?DEBUG -> debug; 57 | ?INFO -> info; 58 | ?NOTICE -> notice; 59 | ?WARNING -> warning; 60 | ?ERROR -> error; 61 | ?CRITICAL -> critical; 62 | ?ALERT -> alert; 63 | ?EMERGENCY -> emergency 64 | end). 65 | 66 | -define(SHOULD_LOG(Sink, Level), 67 | (lager_util:level_to_num(Level) band element(1, lager_config:get({Sink, loglevel}, {?LOG_NONE, []}))) /= 0). 68 | 69 | -define(SHOULD_LOG(Level), 70 | (lager_util:level_to_num(Level) band element(1, lager_config:get(loglevel, {?LOG_NONE, []}))) /= 0). 71 | 72 | -define(NOTIFY(Level, Pid, Format, Args), 73 | gen_event:notify(lager_event, {log, lager_msg:new(io_lib:format(Format, Args), 74 | Level, 75 | [{pid,Pid},{line,?LINE},{file,?FILE},{module,?MODULE}], 76 | [])} 77 | )). 78 | 79 | %% FOR INTERNAL USE ONLY 80 | %% internal non-blocking logging call 81 | %% there's some special handing for when we try to log (usually errors) while 82 | %% lager is still starting. 83 | -ifdef(TEST). 84 | -define(INT_LOG(Level, Format, Args), 85 | case ?SHOULD_LOG(Level) of 86 | true -> 87 | ?NOTIFY(Level, self(), Format, Args); 88 | _ -> 89 | ok 90 | end). 91 | -else. 92 | -define(INT_LOG(Level, Format, Args), 93 | Self = self(), 94 | %% do this in a spawn so we don't cause a deadlock calling gen_event:which_handlers 95 | %% from a gen_event handler 96 | spawn(fun() -> 97 | case catch(gen_event:which_handlers(lager_event)) of 98 | X when X == []; X == {'EXIT', noproc}; X == [lager_backend_throttle] -> 99 | %% there's no handlers yet or lager isn't running, try again 100 | %% in half a second. 101 | timer:sleep(500), 102 | ?NOTIFY(Level, Self, Format, Args); 103 | _ -> 104 | case ?SHOULD_LOG(Level) of 105 | true -> 106 | ?NOTIFY(Level, Self, Format, Args); 107 | _ -> 108 | ok 109 | end 110 | end 111 | end)). 112 | -endif. 113 | 114 | -record(lager_shaper, { 115 | %% how many messages per second we try to deliver 116 | hwm = undefined :: 'undefined' | pos_integer(), 117 | %% how many messages we've received this second 118 | mps = 0 :: non_neg_integer(), 119 | %% the current second 120 | lasttime = os:timestamp() :: erlang:timestamp(), 121 | %% count of dropped messages this second 122 | dropped = 0 :: non_neg_integer() 123 | }). 124 | 125 | -type lager_shaper() :: #lager_shaper{}. 126 | -------------------------------------------------------------------------------- /priv/edoc.css: -------------------------------------------------------------------------------- 1 | /* Baseline rhythm */ 2 | body { 3 | font-size: 16px; 4 | font-family: Helvetica, sans-serif; 5 | margin: 8px; 6 | } 7 | 8 | p { 9 | font-size: 1em; /* 16px */ 10 | line-height: 1.5em; /* 24px */ 11 | margin: 0 0 1.5em 0; 12 | } 13 | 14 | h1 { 15 | font-size: 1.5em; /* 24px */ 16 | line-height: 1em; /* 24px */ 17 | margin-top: 1em; 18 | margin-bottom: 0em; 19 | } 20 | 21 | h2 { 22 | font-size: 1.375em; /* 22px */ 23 | line-height: 1.0909em; /* 24px */ 24 | margin-top: 1.0909em; 25 | margin-bottom: 0em; 26 | } 27 | 28 | h3 { 29 | font-size: 1.25em; /* 20px */ 30 | line-height: 1.2em; /* 24px */ 31 | margin-top: 1.2em; 32 | margin-bottom: 0em; 33 | } 34 | 35 | h4 { 36 | font-size: 1.125em; /* 18px */ 37 | line-height: 1.3333em; /* 24px */ 38 | margin-top: 1.3333em; 39 | margin-bottom: 0em; 40 | } 41 | 42 | .class-for-16px { 43 | font-size: 1em; /* 16px */ 44 | line-height: 1.5em; /* 24px */ 45 | margin-top: 1.5em; 46 | margin-bottom: 0em; 47 | } 48 | 49 | .class-for-14px { 50 | font-size: 0.875em; /* 14px */ 51 | line-height: 1.7143em; /* 24px */ 52 | margin-top: 1.7143em; 53 | margin-bottom: 0em; 54 | } 55 | 56 | ul { 57 | margin: 0 0 1.5em 0; 58 | } 59 | 60 | /* Customizations */ 61 | body { 62 | color: #333; 63 | } 64 | 65 | tt, code, pre { 66 | font-family: "Andale Mono", "Inconsolata", "Monaco", "DejaVu Sans Mono", monospaced; 67 | } 68 | 69 | tt, code { font-size: 0.875em } 70 | 71 | pre { 72 | font-size: 0.875em; /* 14px */ 73 | line-height: 1.7143em; /* 24px */ 74 | margin: 0 1em 1.7143em; 75 | padding: 0 1em; 76 | background: #eee; 77 | } 78 | 79 | .navbar img, hr { display: none } 80 | 81 | table { 82 | border-collapse: collapse; 83 | } 84 | 85 | h1 { 86 | border-left: 0.5em solid #fa0; 87 | padding-left: 0.5em; 88 | } 89 | 90 | h2.indextitle { 91 | font-size: 1.25em; /* 20px */ 92 | line-height: 1.2em; /* 24px */ 93 | margin: -8px -8px 0.6em; 94 | background-color: #fa0; 95 | color: white; 96 | padding: 0.3em; 97 | } 98 | 99 | ul.index { 100 | list-style: none; 101 | margin-left: 0em; 102 | padding-left: 0; 103 | } 104 | 105 | ul.index li { 106 | display: inline; 107 | padding-right: 0.75em 108 | } 109 | 110 | div.spec p { 111 | margin-bottom: 0; 112 | padding-left: 1.25em; 113 | background-color: #eee; 114 | } 115 | 116 | h3.function { 117 | border-left: 0.5em solid #fa0; 118 | padding-left: 0.5em; 119 | background: #fc9; 120 | } 121 | a, a:visited, a:hover, a:active { color: #C60 } 122 | h2 a, h3 a { color: #333 } 123 | 124 | i { 125 | font-size: 0.875em; /* 14px */ 126 | line-height: 1.7143em; /* 24px */ 127 | margin-top: 1.7143em; 128 | margin-bottom: 0em; 129 | font-style: normal; 130 | } 131 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/basho/lager/81eaef0ce98fdbf64ab95665e3bc2ec4b24c7dac/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %% -*- erlang -*- 2 | %% ------------------------------------------------------------------- 3 | %% 4 | %% Copyright (c) 2011-2015 Basho Technologies, Inc. 5 | %% 6 | %% This file is provided to you under the Apache License, 7 | %% Version 2.0 (the "License"); you may not use this file 8 | %% except in compliance with the License. You may obtain 9 | %% a copy of the License at 10 | %% 11 | %% http://www.apache.org/licenses/LICENSE-2.0 12 | %% 13 | %% Unless required by applicable law or agreed to in writing, 14 | %% software distributed under the License is distributed on an 15 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | %% KIND, either express or implied. See the License for the 17 | %% specific language governing permissions and limitations 18 | %% under the License. 19 | %% 20 | %% ------------------------------------------------------------------- 21 | 22 | {erl_opts, [ 23 | {lager_extra_sinks, ['__lager_test_sink']}, 24 | debug_info, 25 | report, 26 | verbose, 27 | warn_deprecated_function, 28 | warn_deprecated_type, 29 | warn_export_all, 30 | warn_export_vars, 31 | warn_obsolete_guard, 32 | warn_untyped_record, 33 | warn_unused_import 34 | % do NOT include warnings_as_errors, as rebar includes these options 35 | % when compiling for eunit, and at least one test module has code that 36 | % is deliberatly broken and will generate an un-maskable warning 37 | ]}. 38 | 39 | {erl_first_files, ["src/lager_util.erl"]}. 40 | 41 | {eunit_opts, [verbose]}. 42 | {eunit_compile_opts, [ 43 | export_all, 44 | 45 | nowarn_untyped_record, 46 | nowarn_export_all 47 | ]}. 48 | 49 | {deps, [ 50 | {goldrush, ".*", {git, "https://github.com/basho/goldrush.git", {tag, "0.1.9"}}} 51 | ]}. 52 | 53 | {xref_checks, []}. 54 | {xref_queries, [{"(XC - UC) || (XU - X - B - lager_default_tracer : Mod - erlang:\"(is_map|map_size)\"/1 - maps:to_list/1)", []}]}. 55 | 56 | {cover_enabled, true}. 57 | {edoc_opts, [{stylesheet_file, "./priv/edoc.css"}]}. 58 | 59 | -------------------------------------------------------------------------------- /src/error_logger_lager_h.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011-2015 Basho Technologies, Inc. All Rights Reserved. 2 | %% 3 | %% This file is provided to you under the Apache License, 4 | %% Version 2.0 (the "License"); you may not use this file 5 | %% except in compliance with the License. You may obtain 6 | %% a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, 11 | %% software distributed under the License is distributed on an 12 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | %% KIND, either express or implied. See the License for the 14 | %% specific language governing permissions and limitations 15 | %% under the License. 16 | 17 | %% @doc A error_logger backend for redirecting events into lager. 18 | %% Error messages and crash logs are also optionally written to a crash log. 19 | 20 | %% @see lager_crash_log 21 | 22 | %% @private 23 | 24 | -module(error_logger_lager_h). 25 | 26 | -include("lager.hrl"). 27 | 28 | -behaviour(gen_event). 29 | 30 | -export([set_high_water/1]). 31 | -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, 32 | code_change/3]). 33 | 34 | -export([format_reason/1, format_mfa/1, format_args/3]). 35 | 36 | -record(state, { 37 | sink :: atom(), 38 | shaper :: lager_shaper(), 39 | %% group leader strategy 40 | groupleader_strategy :: handle | ignore | mirror, 41 | raw :: boolean() 42 | }). 43 | 44 | -define(LOGMSG(Sink, Level, Pid, Msg), 45 | case ?SHOULD_LOG(Sink, Level) of 46 | true -> 47 | _ =lager:log(Sink, Level, Pid, Msg, []), 48 | ok; 49 | _ -> ok 50 | end). 51 | 52 | -define(LOGFMT(Sink, Level, Pid, Fmt, Args), 53 | case ?SHOULD_LOG(Sink, Level) of 54 | true -> 55 | _ = lager:log(Sink, Level, Pid, Fmt, Args), 56 | ok; 57 | _ -> ok 58 | end). 59 | 60 | -ifdef(TEST). 61 | -compile(export_all). 62 | %% Make CRASH synchronous when testing, to avoid timing headaches 63 | -define(CRASH_LOG(Event), 64 | catch(gen_server:call(lager_crash_log, {log, Event}))). 65 | -else. 66 | -define(CRASH_LOG(Event), 67 | gen_server:cast(lager_crash_log, {log, Event})). 68 | -endif. 69 | 70 | set_high_water(N) -> 71 | gen_event:call(error_logger, ?MODULE, {set_high_water, N}, infinity). 72 | 73 | -spec init(any()) -> {ok, #state{}}. 74 | init([HighWaterMark, GlStrategy]) -> 75 | Shaper = #lager_shaper{hwm=HighWaterMark}, 76 | Raw = lager_app:get_env(lager, error_logger_format_raw, false), 77 | Sink = configured_sink(), 78 | {ok, #state{sink=Sink, shaper=Shaper, groupleader_strategy=GlStrategy, raw=Raw}}. 79 | 80 | handle_call({set_high_water, N}, #state{shaper=Shaper} = State) -> 81 | NewShaper = Shaper#lager_shaper{hwm=N}, 82 | {ok, ok, State#state{shaper = NewShaper}}; 83 | handle_call(_Request, State) -> 84 | {ok, unknown_call, State}. 85 | 86 | handle_event(Event, #state{sink=Sink, shaper=Shaper} = State) -> 87 | case lager_util:check_hwm(Shaper) of 88 | {true, 0, NewShaper} -> 89 | eval_gl(Event, State#state{shaper=NewShaper}); 90 | {true, Drop, #lager_shaper{hwm=Hwm} = NewShaper} when Drop > 0 -> 91 | ?LOGFMT(Sink, warning, self(), 92 | "lager_error_logger_h dropped ~p messages in the last second that exceeded the limit of ~p messages/sec", 93 | [Drop, Hwm]), 94 | eval_gl(Event, State#state{shaper=NewShaper}); 95 | {false, _, NewShaper} -> 96 | {ok, State#state{shaper=NewShaper}} 97 | end. 98 | 99 | handle_info(_Info, State) -> 100 | {ok, State}. 101 | 102 | terminate(_Reason, _State) -> 103 | ok. 104 | 105 | 106 | code_change(_OldVsn, {state, Shaper, GLStrategy}, _Extra) -> 107 | Raw = lager_app:get_env(lager, error_logger_format_raw, false), 108 | {ok, #state{ 109 | sink=configured_sink(), 110 | shaper=Shaper, 111 | groupleader_strategy=GLStrategy, 112 | raw=Raw 113 | }}; 114 | code_change(_OldVsn, {state, Sink, Shaper, GLS}, _Extra) -> 115 | Raw = lager_app:get_env(lager, error_logger_format_raw, false), 116 | {ok, #state{sink=Sink, shaper=Shaper, groupleader_strategy=GLS, raw=Raw}}; 117 | code_change(_OldVsn, State, _Extra) -> 118 | {ok, State}. 119 | 120 | %% internal functions 121 | 122 | configured_sink() -> 123 | case proplists:get_value(?ERROR_LOGGER_SINK, lager_app:get_env(lager, extra_sinks, [])) of 124 | undefined -> ?DEFAULT_SINK; 125 | _ -> ?ERROR_LOGGER_SINK 126 | end. 127 | 128 | eval_gl(Event, #state{groupleader_strategy=GlStrategy0}=State) when is_pid(element(2, Event)) -> 129 | case element(2, Event) of 130 | GL when node(GL) =/= node(), GlStrategy0 =:= ignore -> 131 | gen_event:notify({error_logger, node(GL)}, Event), 132 | {ok, State}; 133 | GL when node(GL) =/= node(), GlStrategy0 =:= mirror -> 134 | gen_event:notify({error_logger, node(GL)}, Event), 135 | log_event(Event, State); 136 | _ -> 137 | log_event(Event, State) 138 | end; 139 | eval_gl(Event, State) -> 140 | log_event(Event, State). 141 | 142 | log_event(Event, #state{sink=Sink} = State) -> 143 | case Event of 144 | {error, _GL, {Pid, Fmt, Args}} -> 145 | FormatRaw = State#state.raw, 146 | case {FormatRaw, Fmt} of 147 | {false, "** Generic server "++_} -> 148 | %% gen_server terminate 149 | [Name, _Msg, _State, Reason] = Args, 150 | ?CRASH_LOG(Event), 151 | ?LOGFMT(Sink, error, Pid, "gen_server ~w terminated with reason: ~s", 152 | [Name, format_reason(Reason)]); 153 | {false, "** State machine "++_} -> 154 | %% gen_fsm terminate 155 | [Name, _Msg, StateName, _StateData, Reason] = Args, 156 | ?CRASH_LOG(Event), 157 | ?LOGFMT(Sink, error, Pid, "gen_fsm ~w in state ~w terminated with reason: ~s", 158 | [Name, StateName, format_reason(Reason)]); 159 | {false, "** gen_event handler"++_} -> 160 | %% gen_event handler terminate 161 | [ID, Name, _Msg, _State, Reason] = Args, 162 | ?CRASH_LOG(Event), 163 | ?LOGFMT(Sink, error, Pid, "gen_event ~w installed in ~w terminated with reason: ~s", 164 | [ID, Name, format_reason(Reason)]); 165 | {false, "** Cowboy handler"++_} -> 166 | %% Cowboy HTTP server error 167 | ?CRASH_LOG(Event), 168 | case Args of 169 | [Module, Function, Arity, _Request, _State] -> 170 | %% we only get the 5-element list when its a non-exported function 171 | ?LOGFMT(Sink, error, Pid, 172 | "Cowboy handler ~p terminated with reason: call to undefined function ~p:~p/~p", 173 | [Module, Module, Function, Arity]); 174 | [Module, Function, Arity, _Class, Reason | Tail] -> 175 | %% any other cowboy error_format list *always* ends with the stacktrace 176 | StackTrace = lists:last(Tail), 177 | ?LOGFMT(Sink, error, Pid, 178 | "Cowboy handler ~p terminated in ~p:~p/~p with reason: ~s", 179 | [Module, Module, Function, Arity, format_reason({Reason, StackTrace})]) 180 | end; 181 | {false, "Ranch listener "++_} -> 182 | %% Ranch errors 183 | ?CRASH_LOG(Event), 184 | case Args of 185 | [Ref, _Protocol, Worker, {[{reason, Reason}, {mfa, {Module, Function, Arity}}, {stacktrace, StackTrace} | _], _}] -> 186 | ?LOGFMT(Sink, error, Worker, 187 | "Ranch listener ~p terminated in ~p:~p/~p with reason: ~s", 188 | [Ref, Module, Function, Arity, format_reason({Reason, StackTrace})]); 189 | [Ref, _Protocol, Worker, Reason] -> 190 | ?LOGFMT(Sink, error, Worker, 191 | "Ranch listener ~p terminated with reason: ~s", 192 | [Ref, format_reason(Reason)]) 193 | end; 194 | {false, "webmachine error"++_} -> 195 | %% Webmachine HTTP server error 196 | ?CRASH_LOG(Event), 197 | [Path, Error] = Args, 198 | %% webmachine likes to mangle the stack, for some reason 199 | StackTrace = case Error of 200 | {error, {error, Reason, Stack}} -> 201 | {Reason, Stack}; 202 | _ -> 203 | Error 204 | end, 205 | ?LOGFMT(Sink, error, Pid, "Webmachine error at path ~p : ~s", [Path, format_reason(StackTrace)]); 206 | _ -> 207 | ?CRASH_LOG(Event), 208 | ?LOGFMT(Sink, error, Pid, Fmt, Args) 209 | end; 210 | {error_report, _GL, {Pid, std_error, D}} -> 211 | ?CRASH_LOG(Event), 212 | ?LOGMSG(Sink, error, Pid, print_silly_list(D)); 213 | {error_report, _GL, {Pid, supervisor_report, D}} -> 214 | ?CRASH_LOG(Event), 215 | case lists:sort(D) of 216 | [{errorContext, Ctx}, {offender, Off}, {reason, Reason}, {supervisor, Name}] -> 217 | Offender = format_offender(Off), 218 | ?LOGFMT(Sink, error, Pid, 219 | "Supervisor ~w had child ~s exit with reason ~s in context ~w", 220 | [supervisor_name(Name), Offender, format_reason(Reason), Ctx]); 221 | _ -> 222 | ?LOGMSG(Sink, error, Pid, "SUPERVISOR REPORT " ++ print_silly_list(D)) 223 | end; 224 | {error_report, _GL, {Pid, crash_report, [Self, Neighbours]}} -> 225 | ?CRASH_LOG(Event), 226 | ?LOGMSG(Sink, error, Pid, "CRASH REPORT " ++ format_crash_report(Self, Neighbours)); 227 | {warning_msg, _GL, {Pid, Fmt, Args}} -> 228 | ?LOGFMT(Sink, warning, Pid, Fmt, Args); 229 | {warning_report, _GL, {Pid, std_warning, Report}} -> 230 | ?LOGMSG(Sink, warning, Pid, print_silly_list(Report)); 231 | {info_msg, _GL, {Pid, Fmt, Args}} -> 232 | ?LOGFMT(Sink, info, Pid, Fmt, Args); 233 | {info_report, _GL, {Pid, std_info, D}} when is_list(D) -> 234 | Details = lists:sort(D), 235 | case Details of 236 | [{application, App}, {exited, Reason}, {type, _Type}] -> 237 | case application:get_env(lager, suppress_application_start_stop) of 238 | {ok, true} when Reason == stopped -> 239 | ok; 240 | _ -> 241 | ?LOGFMT(Sink, info, Pid, "Application ~w exited with reason: ~s", 242 | [App, format_reason(Reason)]) 243 | end; 244 | _ -> 245 | ?LOGMSG(Sink, info, Pid, print_silly_list(D)) 246 | end; 247 | {info_report, _GL, {Pid, std_info, D}} -> 248 | ?LOGFMT(Sink, info, Pid, "~w", [D]); 249 | {info_report, _GL, {P, progress, D}} -> 250 | Details = lists:sort(D), 251 | case Details of 252 | [{application, App}, {started_at, Node}] -> 253 | case application:get_env(lager, suppress_application_start_stop) of 254 | {ok, true} -> 255 | ok; 256 | _ -> 257 | ?LOGFMT(Sink, info, P, "Application ~w started on node ~w", 258 | [App, Node]) 259 | end; 260 | [{started, Started}, {supervisor, Name}] -> 261 | MFA = format_mfa(get_value(mfargs, Started)), 262 | Pid = get_value(pid, Started), 263 | ?LOGFMT(Sink, debug, P, "Supervisor ~w started ~s at pid ~w", 264 | [supervisor_name(Name), MFA, Pid]); 265 | _ -> 266 | ?LOGMSG(Sink, info, P, "PROGRESS REPORT " ++ print_silly_list(D)) 267 | end; 268 | _ -> 269 | ?LOGFMT(Sink, warning, self(), "Unexpected error_logger event ~w", [Event]) 270 | end, 271 | {ok, State}. 272 | 273 | format_crash_report(Report, Neighbours) -> 274 | Name = case get_value(registered_name, Report, []) of 275 | [] -> 276 | %% process_info(Pid, registered_name) returns [] for unregistered processes 277 | get_value(pid, Report); 278 | Atom -> Atom 279 | end, 280 | {Class, Reason, Trace} = get_value(error_info, Report), 281 | ReasonStr = format_reason({Reason, Trace}), 282 | Type = case Class of 283 | exit -> "exited"; 284 | _ -> "crashed" 285 | end, 286 | io_lib:format("Process ~w with ~w neighbours ~s with reason: ~s", 287 | [Name, length(Neighbours), Type, ReasonStr]). 288 | 289 | format_offender(Off) -> 290 | case get_value(mfargs, Off) of 291 | undefined -> 292 | %% supervisor_bridge 293 | io_lib:format("at module ~w at ~w", 294 | [get_value(mod, Off), get_value(pid, Off)]); 295 | MFArgs -> 296 | %% regular supervisor 297 | MFA = format_mfa(MFArgs), 298 | 299 | %% In 2014 the error report changed from `name' to 300 | %% `id', so try that first. 301 | Name = case get_value(id, Off) of 302 | undefined -> 303 | get_value(name, Off); 304 | Id -> 305 | Id 306 | end, 307 | io_lib:format("~p started with ~s at ~w", 308 | [Name, MFA, get_value(pid, Off)]) 309 | end. 310 | 311 | format_reason({'function not exported', [{M, F, A},MFA|_]}) -> 312 | ["call to undefined function ", format_mfa({M, F, length(A)}), 313 | " from ", format_mfa(MFA)]; 314 | format_reason({'function not exported', [{M, F, A, _Props},MFA|_]}) -> 315 | %% R15 line numbers 316 | ["call to undefined function ", format_mfa({M, F, length(A)}), 317 | " from ", format_mfa(MFA)]; 318 | format_reason({undef, [MFA|_]}) -> 319 | ["call to undefined function ", format_mfa(MFA)]; 320 | format_reason({bad_return, {_MFA, {'EXIT', Reason}}}) -> 321 | format_reason(Reason); 322 | format_reason({bad_return, {MFA, Val}}) -> 323 | ["bad return value ", print_val(Val), " from ", format_mfa(MFA)]; 324 | format_reason({bad_return_value, Val}) -> 325 | ["bad return value: ", print_val(Val)]; 326 | format_reason({{bad_return_value, Val}, MFA}) -> 327 | ["bad return value: ", print_val(Val), " in ", format_mfa(MFA)]; 328 | format_reason({{badrecord, Record}, [MFA|_]}) -> 329 | ["bad record ", print_val(Record), " in ", format_mfa(MFA)]; 330 | format_reason({{case_clause, Val}, [MFA|_]}) -> 331 | ["no case clause matching ", print_val(Val), " in ", format_mfa(MFA)]; 332 | format_reason({function_clause, [MFA|_]}) -> 333 | ["no function clause matching ", format_mfa(MFA)]; 334 | format_reason({if_clause, [MFA|_]}) -> 335 | ["no true branch found while evaluating if expression in ", format_mfa(MFA)]; 336 | format_reason({{try_clause, Val}, [MFA|_]}) -> 337 | ["no try clause matching ", print_val(Val), " in ", format_mfa(MFA)]; 338 | format_reason({badarith, [MFA|_]}) -> 339 | ["bad arithmetic expression in ", format_mfa(MFA)]; 340 | format_reason({{badmatch, Val}, [MFA|_]}) -> 341 | ["no match of right hand value ", print_val(Val), " in ", format_mfa(MFA)]; 342 | format_reason({emfile, _Trace}) -> 343 | "maximum number of file descriptors exhausted, check ulimit -n"; 344 | format_reason({system_limit, [{M, F, _}|_] = Trace}) -> 345 | Limit = case {M, F} of 346 | {erlang, open_port} -> 347 | "maximum number of ports exceeded"; 348 | {erlang, spawn} -> 349 | "maximum number of processes exceeded"; 350 | {erlang, spawn_opt} -> 351 | "maximum number of processes exceeded"; 352 | {erlang, list_to_atom} -> 353 | "tried to create an atom larger than 255, or maximum atom count exceeded"; 354 | {ets, new} -> 355 | "maximum number of ETS tables exceeded"; 356 | _ -> 357 | {Str, _} = lager_trunc_io:print(Trace, 500), 358 | Str 359 | end, 360 | ["system limit: ", Limit]; 361 | format_reason({badarg, [MFA,MFA2|_]}) -> 362 | case MFA of 363 | {_M, _F, A, _Props} when is_list(A) -> 364 | %% R15 line numbers 365 | ["bad argument in call to ", format_mfa(MFA), " in ", format_mfa(MFA2)]; 366 | {_M, _F, A} when is_list(A) -> 367 | ["bad argument in call to ", format_mfa(MFA), " in ", format_mfa(MFA2)]; 368 | _ -> 369 | %% seems to be generated by a bad call to a BIF 370 | ["bad argument in ", format_mfa(MFA)] 371 | end; 372 | format_reason({{badarg, Stack}, _}) -> 373 | format_reason({badarg, Stack}); 374 | format_reason({{badarity, {Fun, Args}}, [MFA|_]}) -> 375 | {arity, Arity} = lists:keyfind(arity, 1, erlang:fun_info(Fun)), 376 | [io_lib:format("fun called with wrong arity of ~w instead of ~w in ", 377 | [length(Args), Arity]), format_mfa(MFA)]; 378 | format_reason({noproc, MFA}) -> 379 | ["no such process or port in call to ", format_mfa(MFA)]; 380 | format_reason({{badfun, Term}, [MFA|_]}) -> 381 | ["bad function ", print_val(Term), " in ", format_mfa(MFA)]; 382 | format_reason({Reason, [{M, F, A}|_]}) when is_atom(M), is_atom(F), is_integer(A) -> 383 | [format_reason(Reason), " in ", format_mfa({M, F, A})]; 384 | format_reason({Reason, [{M, F, A, Props}|_]}) when is_atom(M), is_atom(F), is_integer(A), is_list(Props) -> 385 | %% line numbers 386 | [format_reason(Reason), " in ", format_mfa({M, F, A, Props})]; 387 | format_reason(Reason) -> 388 | {Str, _} = lager_trunc_io:print(Reason, 500), 389 | Str. 390 | 391 | format_mfa({M, F, A}) when is_list(A) -> 392 | {FmtStr, Args} = format_args(A, [], []), 393 | io_lib:format("~w:~w("++FmtStr++")", [M, F | Args]); 394 | format_mfa({M, F, A}) when is_integer(A) -> 395 | io_lib:format("~w:~w/~w", [M, F, A]); 396 | format_mfa({M, F, A, Props}) when is_list(Props) -> 397 | case get_value(line, Props) of 398 | undefined -> 399 | format_mfa({M, F, A}); 400 | Line -> 401 | [format_mfa({M, F, A}), io_lib:format(" line ~w", [Line])] 402 | end; 403 | format_mfa([{M, F, A}, _]) -> 404 | %% this kind of weird stacktrace can be generated by a uncaught throw in a gen_server 405 | format_mfa({M, F, A}); 406 | format_mfa([{M, F, A, Props}, _]) when is_list(Props) -> 407 | %% this kind of weird stacktrace can be generated by a uncaught throw in a gen_server 408 | format_mfa({M, F, A, Props}); 409 | format_mfa(Other) -> 410 | io_lib:format("~w", [Other]). 411 | 412 | format_args([], FmtAcc, ArgsAcc) -> 413 | {string:join(lists:reverse(FmtAcc), ", "), lists:reverse(ArgsAcc)}; 414 | format_args([H|T], FmtAcc, ArgsAcc) -> 415 | {Str, _} = lager_trunc_io:print(H, 100), 416 | format_args(T, ["~s"|FmtAcc], [Str|ArgsAcc]). 417 | 418 | print_silly_list(L) when is_list(L) -> 419 | case lager_stdlib:string_p(L) of 420 | true -> 421 | lager_trunc_io:format("~s", [L], ?DEFAULT_TRUNCATION); 422 | _ -> 423 | print_silly_list(L, [], []) 424 | end; 425 | print_silly_list(L) -> 426 | {Str, _} = lager_trunc_io:print(L, ?DEFAULT_TRUNCATION), 427 | Str. 428 | 429 | print_silly_list([], Fmt, Acc) -> 430 | lager_trunc_io:format(string:join(lists:reverse(Fmt), ", "), 431 | lists:reverse(Acc), ?DEFAULT_TRUNCATION); 432 | print_silly_list([{K,V}|T], Fmt, Acc) -> 433 | print_silly_list(T, ["~p: ~p" | Fmt], [V, K | Acc]); 434 | print_silly_list([H|T], Fmt, Acc) -> 435 | print_silly_list(T, ["~p" | Fmt], [H | Acc]). 436 | 437 | print_val(Val) -> 438 | {Str, _} = lager_trunc_io:print(Val, 500), 439 | Str. 440 | 441 | 442 | %% @doc Faster than proplists, but with the same API as long as you don't need to 443 | %% handle bare atom keys 444 | get_value(Key, Value) -> 445 | get_value(Key, Value, undefined). 446 | 447 | get_value(Key, List, Default) -> 448 | case lists:keyfind(Key, 1, List) of 449 | false -> Default; 450 | {Key, Value} -> Value 451 | end. 452 | 453 | supervisor_name({local, Name}) -> Name; 454 | supervisor_name(Name) -> Name. 455 | -------------------------------------------------------------------------------- /src/lager.app.src: -------------------------------------------------------------------------------- 1 | %% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- 2 | %% ex: ts=4 sw=4 et 3 | {application, lager, 4 | [ 5 | {description, "Erlang logging framework"}, 6 | {vsn, "3.2.2"}, 7 | {modules, []}, 8 | {applications, [ 9 | kernel, 10 | stdlib, 11 | goldrush 12 | ]}, 13 | {registered, [lager_sup, lager_event, lager_crash_log, lager_handler_watcher_sup]}, 14 | {mod, {lager_app, []}}, 15 | {env, [ 16 | %% Note: application:start(lager) overwrites previously defined environment variables 17 | %% thus declaration of default handlers is done at lager_app.erl 18 | 19 | %% What colors to use with what log levels 20 | {colored, false}, 21 | {colors, [ 22 | {debug, "\e[0;38m" }, 23 | {info, "\e[1;37m" }, 24 | {notice, "\e[1;36m" }, 25 | {warning, "\e[1;33m" }, 26 | {error, "\e[1;31m" }, 27 | {critical, "\e[1;35m" }, 28 | {alert, "\e[1;44m" }, 29 | {emergency, "\e[1;41m" } 30 | 31 | ]}, 32 | 33 | %% Whether to write a crash log, and where. False means no crash logger. 34 | {crash_log, "log/crash.log"}, 35 | %% Maximum size in bytes of events in the crash log - defaults to 65536 36 | {crash_log_msg_size, 65536}, 37 | %% Maximum size of the crash log in bytes, before its rotated, set 38 | %% to 0 to disable rotation - default is 0 39 | {crash_log_size, 10485760}, 40 | %% What time to rotate the crash log - default is no time 41 | %% rotation. See the README for a description of this format. 42 | {crash_log_date, "$D0"}, 43 | %% Number of rotated crash logs to keep, 0 means keep only the 44 | %% current one - default is 0 45 | {crash_log_count, 5}, 46 | %% Whether to redirect error_logger messages into the default lager_event sink - defaults to true 47 | {error_logger_redirect, true}, 48 | %% How many messages per second to allow from error_logger before we start dropping them 49 | {error_logger_hwm, 50}, 50 | %% How big the gen_event mailbox can get before it is 51 | %% switched into sync mode. This value only applies to 52 | %% the default sink; extra sinks can supply their own. 53 | {async_threshold, 20}, 54 | %% Switch back to async mode, when gen_event mailbox size 55 | %% decrease from `async_threshold' to async_threshold - 56 | %% async_threshold_window. This value only applies to the 57 | %% default sink; extra sinks can supply their own. 58 | {async_threshold_window, 5} 59 | ]} 60 | ]}. 61 | -------------------------------------------------------------------------------- /src/lager_app.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. 2 | %% 3 | %% This file is provided to you under the Apache License, 4 | %% Version 2.0 (the "License"); you may not use this file 5 | %% except in compliance with the License. You may obtain 6 | %% a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, 11 | %% software distributed under the License is distributed on an 12 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | %% KIND, either express or implied. See the License for the 14 | %% specific language governing permissions and limitations 15 | %% under the License. 16 | 17 | %% @doc Lager's application module. Not a lot to see here. 18 | 19 | %% @private 20 | 21 | -module(lager_app). 22 | 23 | -behaviour(application). 24 | -include("lager.hrl"). 25 | -ifdef(TEST). 26 | -compile([export_all]). 27 | -include_lib("eunit/include/eunit.hrl"). 28 | -endif. 29 | -export([start/0, 30 | start/2, 31 | start_handler/3, 32 | configure_sink/2, 33 | stop/1, 34 | boot/1]). 35 | 36 | %% The `application:get_env/3` compatibility wrapper is useful 37 | %% for other modules 38 | -export([get_env/3]). 39 | 40 | -define(FILENAMES, '__lager_file_backend_filenames'). 41 | -define(THROTTLE, lager_backend_throttle). 42 | -define(DEFAULT_HANDLER_CONF, 43 | [{lager_console_backend, info}, 44 | {lager_file_backend, 45 | [{file, "log/error.log"}, {level, error}, 46 | {size, 10485760}, {date, "$D0"}, {count, 5}] 47 | }, 48 | {lager_file_backend, 49 | [{file, "log/console.log"}, {level, info}, 50 | {size, 10485760}, {date, "$D0"}, {count, 5}] 51 | } 52 | ]). 53 | 54 | start() -> 55 | application:start(lager). 56 | 57 | start_throttle(Sink, Threshold, Window) -> 58 | _ = supervisor:start_child(lager_handler_watcher_sup, 59 | [Sink, ?THROTTLE, [Threshold, Window]]), 60 | ok. 61 | 62 | determine_async_behavior(_Sink, undefined, _Window) -> 63 | ok; 64 | determine_async_behavior(_Sink, Threshold, _Window) when not is_integer(Threshold) orelse Threshold < 0 -> 65 | error_logger:error_msg("Invalid value for 'async_threshold': ~p~n", 66 | [Threshold]), 67 | throw({error, bad_config}); 68 | determine_async_behavior(Sink, Threshold, undefined) -> 69 | start_throttle(Sink, Threshold, erlang:trunc(Threshold * 0.2)); 70 | determine_async_behavior(_Sink, Threshold, Window) when not is_integer(Window) orelse Window > Threshold orelse Window < 0 -> 71 | error_logger:error_msg( 72 | "Invalid value for 'async_threshold_window': ~p~n", [Window]), 73 | throw({error, bad_config}); 74 | determine_async_behavior(Sink, Threshold, Window) -> 75 | start_throttle(Sink, Threshold, Window). 76 | 77 | start_handlers(_Sink, undefined) -> 78 | ok; 79 | start_handlers(_Sink, Handlers) when not is_list(Handlers) -> 80 | error_logger:error_msg( 81 | "Invalid value for 'handlers' (must be list): ~p~n", [Handlers]), 82 | throw({error, bad_config}); 83 | start_handlers(Sink, Handlers) -> 84 | %% handlers failing to start are handled in the handler_watcher 85 | lager_config:global_set(handlers, 86 | lager_config:global_get(handlers, []) ++ 87 | lists:map(fun({Module, Config}) -> 88 | check_handler_config(Module, Config), 89 | start_handler(Sink, Module, Config); 90 | (_) -> 91 | throw({error, bad_config}) 92 | end, 93 | expand_handlers(Handlers))), 94 | ok. 95 | 96 | start_handler(Sink, Module, Config) -> 97 | {ok, Watcher} = supervisor:start_child(lager_handler_watcher_sup, 98 | [Sink, Module, Config]), 99 | {Module, Watcher, Sink}. 100 | 101 | check_handler_config({lager_file_backend, F}, Config) when is_list(Config); is_tuple(Config) -> 102 | Fs = case get(?FILENAMES) of 103 | undefined -> ordsets:new(); 104 | X -> X 105 | end, 106 | case ordsets:is_element(F, Fs) of 107 | true -> 108 | error_logger:error_msg( 109 | "Cannot have same file (~p) in multiple file backends~n", [F]), 110 | throw({error, bad_config}); 111 | false -> 112 | put(?FILENAMES, 113 | ordsets:add_element(F, Fs)) 114 | end, 115 | ok; 116 | check_handler_config(_Handler, Config) when is_list(Config) orelse is_atom(Config) -> 117 | ok; 118 | check_handler_config(Handler, _BadConfig) -> 119 | throw({error, {bad_config, Handler}}). 120 | 121 | clean_up_config_checks() -> 122 | erase(?FILENAMES). 123 | 124 | interpret_hwm(undefined) -> 125 | undefined; 126 | interpret_hwm(HWM) when not is_integer(HWM) orelse HWM < 0 -> 127 | _ = lager:log(warning, self(), "Invalid error_logger high water mark: ~p, disabling", [HWM]), 128 | undefined; 129 | interpret_hwm(HWM) -> 130 | HWM. 131 | 132 | maybe_install_sink_killer(_Sink, undefined, _ReinstallTimer) -> ok; 133 | maybe_install_sink_killer(Sink, HWM, undefined) -> maybe_install_sink_killer(Sink, HWM, 5000); 134 | maybe_install_sink_killer(Sink, HWM, ReinstallTimer) when is_integer(HWM) andalso is_integer(ReinstallTimer) 135 | andalso HWM >= 0 andalso ReinstallTimer >= 0 -> 136 | _ = supervisor:start_child(lager_handler_watcher_sup, [Sink, lager_manager_killer, 137 | [HWM, ReinstallTimer]]); 138 | maybe_install_sink_killer(_Sink, HWM, ReinstallTimer) -> 139 | error_logger:error_msg("Invalid value for 'killer_hwm': ~p or 'killer_reinstall_after': ~p", [HWM, ReinstallTimer]), 140 | throw({error, bad_config}). 141 | 142 | -spec start_error_logger_handler(boolean(), pos_integer(), list()) -> list(). 143 | start_error_logger_handler(false, _HWM, _Whitelist) -> 144 | []; 145 | start_error_logger_handler(true, HWM, WhiteList) -> 146 | GlStrategy = case application:get_env(lager, error_logger_groupleader_strategy) of 147 | undefined -> 148 | handle; 149 | {ok, GlStrategy0} when 150 | GlStrategy0 =:= handle; 151 | GlStrategy0 =:= ignore; 152 | GlStrategy0 =:= mirror -> 153 | GlStrategy0; 154 | {ok, BadGlStrategy} -> 155 | error_logger:error_msg( 156 | "Invalid value for 'error_logger_groupleader_strategy': ~p~n", 157 | [BadGlStrategy]), 158 | throw({error, bad_config}) 159 | end, 160 | 161 | 162 | _ = case supervisor:start_child(lager_handler_watcher_sup, [error_logger, error_logger_lager_h, [HWM, GlStrategy]]) of 163 | {ok, _} -> 164 | [begin error_logger:delete_report_handler(X), X end || 165 | X <- gen_event:which_handlers(error_logger) -- [error_logger_lager_h | WhiteList]]; 166 | {error, _} -> 167 | [] 168 | end, 169 | 170 | Handlers = case application:get_env(lager, handlers) of 171 | undefined -> 172 | [{lager_console_backend, info}, 173 | {lager_file_backend, [{file, "log/error.log"}, {level, error}, {size, 10485760}, {date, "$D0"}, {count, 5}]}, 174 | {lager_file_backend, [{file, "log/console.log"}, {level, info}, {size, 10485760}, {date, "$D0"}, {count, 5}]}]; 175 | {ok, Val} -> 176 | Val 177 | end, 178 | Handlers. 179 | 180 | configure_sink(Sink, SinkDef) -> 181 | lager_config:new_sink(Sink), 182 | ChildId = lager_util:make_internal_sink_name(Sink), 183 | _ = supervisor:start_child(lager_sup, 184 | {ChildId, 185 | {gen_event, start_link, 186 | [{local, Sink}]}, 187 | permanent, 5000, worker, dynamic}), 188 | determine_async_behavior(Sink, proplists:get_value(async_threshold, SinkDef), 189 | proplists:get_value(async_threshold_window, SinkDef) 190 | ), 191 | _ = maybe_install_sink_killer(Sink, proplists:get_value(killer_hwm, SinkDef), 192 | proplists:get_value(killer_reinstall_after, SinkDef)), 193 | start_handlers(Sink, 194 | proplists:get_value(handlers, SinkDef, [])), 195 | 196 | lager:update_loglevel_config(Sink). 197 | 198 | 199 | configure_extra_sinks(Sinks) -> 200 | lists:foreach(fun({Sink, Proplist}) -> configure_sink(Sink, Proplist) end, 201 | Sinks). 202 | 203 | -spec get_env(atom(), atom()) -> term(). 204 | get_env(Application, Key) -> 205 | get_env(Application, Key, undefined). 206 | 207 | %% R15 doesn't know about application:get_env/3 208 | -spec get_env(atom(), atom(), term()) -> term(). 209 | get_env(Application, Key, Default) -> 210 | get_env_default(application:get_env(Application, Key), Default). 211 | 212 | -spec get_env_default('undefined' | {'ok', term()}, term()) -> term(). 213 | get_env_default(undefined, Default) -> 214 | Default; 215 | get_env_default({ok, Value}, _Default) -> 216 | Value. 217 | 218 | start(_StartType, _StartArgs) -> 219 | {ok, Pid} = lager_sup:start_link(), 220 | SavedHandlers = boot(), 221 | _ = boot('__all_extra'), 222 | _ = boot('__traces'), 223 | clean_up_config_checks(), 224 | {ok, Pid, SavedHandlers}. 225 | 226 | boot() -> 227 | %% Handle the default sink. 228 | determine_async_behavior(?DEFAULT_SINK, 229 | get_env(lager, async_threshold), 230 | get_env(lager, async_threshold_window)), 231 | 232 | _ = maybe_install_sink_killer(?DEFAULT_SINK, get_env(lager, killer_hwm), 233 | get_env(lager, killer_reinstall_after)), 234 | 235 | start_handlers(?DEFAULT_SINK, 236 | get_env(lager, handlers, ?DEFAULT_HANDLER_CONF)), 237 | 238 | lager:update_loglevel_config(?DEFAULT_SINK), 239 | 240 | SavedHandlers = start_error_logger_handler( 241 | get_env(lager, error_logger_redirect, true), 242 | interpret_hwm(get_env(lager, error_logger_hwm, 0)), 243 | get_env(lager, error_logger_whitelist, []) 244 | ), 245 | 246 | SavedHandlers. 247 | 248 | boot('__traces') -> 249 | _ = lager_util:trace_filter(none), 250 | ok = add_configured_traces(); 251 | 252 | boot('__all_extra') -> 253 | configure_extra_sinks(get_env(lager, extra_sinks, [])); 254 | 255 | boot(?DEFAULT_SINK) -> boot(); 256 | boot(Sink) -> 257 | AllSinksDef = get_env(lager, extra_sinks, []), 258 | boot_sink(Sink, lists:keyfind(Sink, 1, AllSinksDef)). 259 | 260 | boot_sink(Sink, {Sink, Def}) -> 261 | configure_sink(Sink, Def); 262 | boot_sink(Sink, false) -> 263 | configure_sink(Sink, []). 264 | 265 | stop(Handlers) -> 266 | lists:foreach(fun(Handler) -> 267 | error_logger:add_report_handler(Handler) 268 | end, Handlers). 269 | 270 | expand_handlers([]) -> 271 | []; 272 | expand_handlers([{lager_file_backend, [{Key, _Value}|_]=Config}|T]) when is_atom(Key) -> 273 | %% this is definitely a new-style config, no expansion needed 274 | [maybe_make_handler_id(lager_file_backend, Config) | expand_handlers(T)]; 275 | expand_handlers([{lager_file_backend, Configs}|T]) -> 276 | ?INT_LOG(notice, "Deprecated lager_file_backend config detected, please consider updating it", []), 277 | [ {lager_file_backend:config_to_id(Config), Config} || Config <- Configs] ++ 278 | expand_handlers(T); 279 | expand_handlers([{Mod, Config}|T]) when is_atom(Mod) -> 280 | [maybe_make_handler_id(Mod, Config) | expand_handlers(T)]; 281 | expand_handlers([H|T]) -> 282 | [H | expand_handlers(T)]. 283 | 284 | add_configured_traces() -> 285 | Traces = case application:get_env(lager, traces) of 286 | undefined -> 287 | []; 288 | {ok, TraceVal} -> 289 | TraceVal 290 | end, 291 | 292 | lists:foreach(fun start_configured_trace/1, Traces), 293 | ok. 294 | 295 | start_configured_trace({Handler, Filter}) -> 296 | {ok, _} = lager:trace(Handler, Filter); 297 | start_configured_trace({Handler, Filter, Level}) when is_atom(Level) -> 298 | {ok, _} = lager:trace(Handler, Filter, Level). 299 | 300 | maybe_make_handler_id(Mod, Config) -> 301 | %% Allow the backend to generate a gen_event handler id, if it wants to. 302 | %% We don't use erlang:function_exported here because that requires the module 303 | %% already be loaded, which is unlikely at this phase of startup. Using code:load 304 | %% caused undesirable side-effects with generating code-coverage reports. 305 | try Mod:config_to_id(Config) of 306 | Id -> 307 | {Id, Config} 308 | catch 309 | error:undef -> 310 | {Mod, Config} 311 | end. 312 | 313 | -ifdef(TEST). 314 | application_config_mangling_test_() -> 315 | [ 316 | {"Explode the file backend handlers", 317 | ?_assertMatch( 318 | [{lager_console_backend, info}, 319 | {{lager_file_backend,"error.log"},{"error.log",error,10485760, "$D0",5}}, 320 | {{lager_file_backend,"console.log"},{"console.log",info,10485760, "$D0",5}} 321 | ], 322 | expand_handlers([{lager_console_backend, info}, 323 | {lager_file_backend, [ 324 | {"error.log", error, 10485760, "$D0", 5}, 325 | {"console.log", info, 10485760, "$D0", 5} 326 | ]}] 327 | )) 328 | }, 329 | {"Explode the short form of backend file handlers", 330 | ?_assertMatch( 331 | [{lager_console_backend, info}, 332 | {{lager_file_backend,"error.log"},{"error.log",error}}, 333 | {{lager_file_backend,"console.log"},{"console.log",info}} 334 | ], 335 | expand_handlers([{lager_console_backend, info}, 336 | {lager_file_backend, [ 337 | {"error.log", error}, 338 | {"console.log", info} 339 | ]}] 340 | )) 341 | }, 342 | {"Explode with formatter info", 343 | ?_assertMatch( 344 | [{{lager_file_backend,"test.log"}, [{"test.log", debug, 10485760, "$D0", 5},{lager_default_formatter,["[",severity,"] ", message, "\n"]}]}, 345 | {{lager_file_backend,"test2.log"}, [{"test2.log",debug, 10485760, "$D0", 5},{lager_default_formatter,["2>[",severity,"] ", message, "\n"]}]}], 346 | expand_handlers([{lager_file_backend, [ 347 | [{"test.log", debug, 10485760, "$D0", 5},{lager_default_formatter,["[",severity,"] ", message, "\n"]}], 348 | [{"test2.log",debug, 10485760, "$D0", 5},{lager_default_formatter,["2>[",severity,"] ",message, "\n"]}] 349 | ] 350 | }]) 351 | ) 352 | }, 353 | {"Explode short form with short formatter info", 354 | ?_assertMatch( 355 | [{{lager_file_backend,"test.log"}, [{"test.log", debug},{lager_default_formatter,["[",severity,"] ", message, "\n"]}]}, 356 | {{lager_file_backend,"test2.log"}, [{"test2.log",debug},{lager_default_formatter}]}], 357 | expand_handlers([{lager_file_backend, [ 358 | [{"test.log", debug},{lager_default_formatter,["[",severity,"] ", message, "\n"]}], 359 | [{"test2.log",debug},{lager_default_formatter}] 360 | ] 361 | }]) 362 | ) 363 | }, 364 | {"New form needs no expansion", 365 | ?_assertMatch([ 366 | {{lager_file_backend,"test.log"}, [{file, "test.log"}]}, 367 | {{lager_file_backend,"test2.log"}, [{file, "test2.log"}, {level, info}, {sync_on, none}]}, 368 | {{lager_file_backend,"test3.log"}, [{formatter, lager_default_formatter}, {file, "test3.log"}]} 369 | ], 370 | expand_handlers([ 371 | {lager_file_backend, [{file, "test.log"}]}, 372 | {lager_file_backend, [{file, "test2.log"}, {level, info}, {sync_on, none}]}, 373 | {lager_file_backend, [{formatter, lager_default_formatter},{file, "test3.log"}]} 374 | ]) 375 | ) 376 | } 377 | ]. 378 | 379 | check_handler_config_test_() -> 380 | Good = expand_handlers(?DEFAULT_HANDLER_CONF), 381 | Bad = expand_handlers([{lager_console_backend, info}, 382 | {lager_file_backend, [{file, "same_file.log"}]}, 383 | {lager_file_backend, [{file, "same_file.log"}, {level, info}]}]), 384 | AlsoBad = [{lager_logstash_backend, 385 | {level, info}, 386 | {output, {udp, "localhost", 5000}}, 387 | {format, json}, 388 | {json_encoder, jiffy}}], 389 | BadToo = [{fail, {fail}}], 390 | OldSchoolLagerGood = expand_handlers([{lager_console_backend,info}, 391 | {lager_file_backend, [ 392 | {"./log/error.log",error,10485760,"$D0",5}, 393 | {"./log/console.log",info,10485760,"$D0",5}, 394 | {"./log/debug.log",debug,10485760,"$D0",5} 395 | ]}]), 396 | NewConfigMissingList = expand_handlers([{foo_backend, {file, "same_file.log"}}]), 397 | [ 398 | {"lager_file_backend_good", 399 | ?_assertEqual([ok, ok, ok], [ check_handler_config(M,C) || {M,C} <- Good ]) 400 | }, 401 | {"lager_file_backend_bad", 402 | ?_assertThrow({error, bad_config}, [ check_handler_config(M,C) || {M,C} <- Bad ]) 403 | }, 404 | {"Invalid config dies", 405 | ?_assertThrow({error, bad_config}, start_handlers(foo, AlsoBad)) 406 | }, 407 | {"Invalid config dies", 408 | ?_assertThrow({error, {bad_config, _}}, start_handlers(foo, BadToo)) 409 | }, 410 | {"Old Lager config works", 411 | ?_assertEqual([ok, ok, ok, ok], [ check_handler_config(M, C) || {M, C} <- OldSchoolLagerGood]) 412 | }, 413 | {"New Config missing its list should fail", 414 | ?_assertThrow({error, {bad_config, foo_backend}}, [ check_handler_config(M, C) || {M, C} <- NewConfigMissingList]) 415 | } 416 | ]. 417 | -endif. 418 | -------------------------------------------------------------------------------- /src/lager_backend_throttle.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011-2013 Basho Technologies, Inc. All Rights Reserved. 2 | %% 3 | %% This file is provided to you under the Apache License, 4 | %% Version 2.0 (the "License"); you may not use this file 5 | %% except in compliance with the License. You may obtain 6 | %% a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, 11 | %% software distributed under the License is distributed on an 12 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | %% KIND, either express or implied. See the License for the 14 | %% specific language governing permissions and limitations 15 | %% under the License. 16 | 17 | %% @doc A simple gen_event backend used to monitor mailbox size and 18 | %% switch log messages between synchronous and asynchronous modes. 19 | %% A gen_event handler is used because a process getting its own mailbox 20 | %% size doesn't involve getting a lock, and gen_event handlers run in their 21 | %% parent's process. 22 | 23 | -module(lager_backend_throttle). 24 | 25 | -include("lager.hrl"). 26 | 27 | -behaviour(gen_event). 28 | 29 | -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, 30 | code_change/3]). 31 | 32 | %% 33 | %% Allow test code to verify that we're doing the needful. 34 | -ifdef(TEST). 35 | -define(ETS_TABLE, async_threshold_test). 36 | -define(TOGGLE_SYNC(), test_increment(sync_toggled)). 37 | -define(TOGGLE_ASYNC(), test_increment(async_toggled)). 38 | -else. 39 | -define(TOGGLE_SYNC(), true). 40 | -define(TOGGLE_ASYNC(), true). 41 | -endif. 42 | 43 | -record(state, { 44 | sink :: atom(), 45 | hwm :: non_neg_integer(), 46 | window_min :: non_neg_integer(), 47 | async = true :: boolean() 48 | }). 49 | 50 | init([{sink, Sink}, Hwm, Window]) -> 51 | lager_config:set({Sink, async}, true), 52 | {ok, #state{sink=Sink, hwm=Hwm, window_min=Hwm - Window}}. 53 | 54 | 55 | handle_call(get_loglevel, State) -> 56 | {ok, {mask, ?LOG_NONE}, State}; 57 | handle_call({set_loglevel, _Level}, State) -> 58 | {ok, ok, State}; 59 | handle_call(_Request, State) -> 60 | {ok, ok, State}. 61 | 62 | handle_event({log, _Message},State) -> 63 | {message_queue_len, Len} = erlang:process_info(self(), message_queue_len), 64 | case {Len > State#state.hwm, Len < State#state.window_min, State#state.async} of 65 | {true, _, true} -> 66 | %% need to flip to sync mode 67 | ?TOGGLE_SYNC(), 68 | lager_config:set({State#state.sink, async}, false), 69 | {ok, State#state{async=false}}; 70 | {_, true, false} -> 71 | %% need to flip to async mode 72 | ?TOGGLE_ASYNC(), 73 | lager_config:set({State#state.sink, async}, true), 74 | {ok, State#state{async=true}}; 75 | _ -> 76 | %% nothing needs to change 77 | {ok, State} 78 | end; 79 | handle_event(_Event, State) -> 80 | {ok, State}. 81 | 82 | handle_info(_Info, State) -> 83 | {ok, State}. 84 | 85 | %% @private 86 | terminate(_Reason, _State) -> 87 | ok. 88 | 89 | %% @private 90 | code_change(_OldVsn, State, _Extra) -> 91 | {ok, State}. 92 | 93 | -ifdef(TEST). 94 | test_get(Key) -> 95 | get_default(ets:lookup(?ETS_TABLE, Key)). 96 | 97 | test_increment(Key) -> 98 | ets:insert(?ETS_TABLE, 99 | {Key, test_get(Key) + 1}). 100 | 101 | get_default([]) -> 102 | 0; 103 | get_default([{_Key, Value}]) -> 104 | Value. 105 | -endif. 106 | -------------------------------------------------------------------------------- /src/lager_common_test_backend.erl: -------------------------------------------------------------------------------- 1 | -module(lager_common_test_backend). 2 | 3 | -behavior(gen_event). 4 | 5 | %% gen_event callbacks 6 | -export([init/1, 7 | handle_call/2, 8 | handle_event/2, 9 | handle_info/2, 10 | terminate/2, 11 | code_change/3]). 12 | -export([get_logs/0, 13 | bounce/0, 14 | bounce/1]). 15 | 16 | %% holds the log messages for retreival on terminate 17 | -record(state, {level :: {mask, integer()}, 18 | formatter :: atom(), 19 | format_config :: any(), 20 | log = [] :: list()}). 21 | 22 | -include("lager.hrl"). 23 | -define(TERSE_FORMAT,[time, " ", color, "[", severity,"] ", message]). 24 | 25 | %% @doc Before every test, just 26 | %% lager_common_test_backend:bounce(Level) with the log level of your 27 | %% choice. Every message will be passed along to ct:pal for your 28 | %% viewing in the common_test reports. Also, you can call 29 | %% lager_common_test_backend:get_logs/0 to get a list of all log 30 | %% messages this backend has received during your test. You can then 31 | %% search that list for expected log messages. 32 | 33 | 34 | -spec get_logs() -> [iolist()] | {error, term()}. 35 | get_logs() -> 36 | gen_event:call(lager_event, ?MODULE, get_logs, infinity). 37 | 38 | bounce() -> 39 | bounce(error). 40 | 41 | bounce(Level) -> 42 | _ = application:stop(lager), 43 | application:set_env(lager, suppress_application_start_stop, true), 44 | application:set_env(lager, handlers, 45 | [ 46 | {lager_common_test_backend, [Level, false]} 47 | ]), 48 | ok = lager:start(), 49 | %% we care more about getting all of our messages here than being 50 | %% careful with the amount of memory that we're using. 51 | error_logger_lager_h:set_high_water(100000), 52 | ok. 53 | 54 | -spec(init(integer()|atom()|[term()]) -> {ok, #state{}} | {error, atom()}). 55 | %% @private 56 | %% @doc Initializes the event handler 57 | init([Level, true]) -> % for backwards compatibility 58 | init([Level,{lager_default_formatter,[{eol, "\n"}]}]); 59 | init([Level,false]) -> % for backwards compatibility 60 | init([Level,{lager_default_formatter,?TERSE_FORMAT ++ ["\n"]}]); 61 | init([Level,{Formatter,FormatterConfig}]) when is_atom(Formatter) -> 62 | case lists:member(Level, ?LEVELS) of 63 | true -> 64 | {ok, #state{level=lager_util:config_to_mask(Level), 65 | formatter=Formatter, 66 | format_config=FormatterConfig}}; 67 | _ -> 68 | {error, bad_log_level} 69 | end; 70 | init(Level) -> 71 | init([Level,{lager_default_formatter,?TERSE_FORMAT ++ ["\n"]}]). 72 | 73 | -spec(handle_event(tuple(), #state{}) -> {ok, #state{}}). 74 | %% @private 75 | handle_event({log, Message}, 76 | #state{level=L,formatter=Formatter,format_config=FormatConfig,log=Logs} = State) -> 77 | case lager_util:is_loggable(Message,L,?MODULE) of 78 | true -> 79 | Log = Formatter:format(Message,FormatConfig), 80 | ct:pal(Log), 81 | {ok, State#state{log=[Log|Logs]}}; 82 | false -> 83 | {ok, State} 84 | end; 85 | handle_event(Event, State) -> 86 | ct:pal(Event), 87 | {ok, State#state{log = [Event|State#state.log]}}. 88 | 89 | -spec(handle_call(any(), #state{}) -> {ok, any(), #state{}}). 90 | %% @private 91 | %% @doc gets and sets loglevel. This is part of the lager backend api. 92 | handle_call(get_loglevel, #state{level=Level} = State) -> 93 | {ok, Level, State}; 94 | handle_call({set_loglevel, Level}, State) -> 95 | case lists:member(Level, ?LEVELS) of 96 | true -> 97 | {ok, ok, State#state{level=lager_util:config_to_mask(Level)}}; 98 | _ -> 99 | {ok, {error, bad_log_level}, State} 100 | end; 101 | handle_call(get_logs, #state{log = Logs} = State) -> 102 | {ok, lists:reverse(Logs), State}; 103 | handle_call(_, State) -> 104 | {ok, ok, State}. 105 | 106 | -spec(handle_info(any(), #state{}) -> {ok, #state{}}). 107 | %% @private 108 | %% @doc gen_event callback, does nothing. 109 | handle_info(_, State) -> 110 | {ok, State}. 111 | 112 | -spec(code_change(any(), #state{}, any()) -> {ok, #state{}}). 113 | %% @private 114 | %% @doc gen_event callback, does nothing. 115 | code_change(_OldVsn, State, _Extra) -> 116 | {ok, State}. 117 | 118 | -spec(terminate(any(), #state{}) -> {ok, list()}). 119 | %% @doc gen_event callback, does nothing. 120 | terminate(_Reason, #state{log=Logs}) -> 121 | {ok, lists:reverse(Logs)}. 122 | -------------------------------------------------------------------------------- /src/lager_config.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. 2 | %% 3 | %% This file is provided to you under the Apache License, 4 | %% Version 2.0 (the "License"); you may not use this file 5 | %% except in compliance with the License. You may obtain 6 | %% a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, 11 | %% software distributed under the License is distributed on an 12 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | %% KIND, either express or implied. See the License for the 14 | %% specific language governing permissions and limitations 15 | %% under the License. 16 | 17 | %% @doc Helper functions for working with lager's runtime config 18 | 19 | -module(lager_config). 20 | 21 | -include("lager.hrl"). 22 | 23 | -export([new/0, new_sink/1, get/1, get/2, set/2, 24 | global_get/1, global_get/2, global_set/2]). 25 | 26 | -define(TBL, lager_config). 27 | -define(GLOBAL, '_global'). 28 | 29 | %% For multiple sinks, the key is now the registered event name and the old key 30 | %% as a tuple. 31 | %% 32 | %% {{lager_event, loglevel}, Value} instead of {loglevel, Value} 33 | 34 | new() -> 35 | %% set up the ETS configuration table 36 | _ = try ets:new(?TBL, [named_table, public, set, {keypos, 1}, {read_concurrency, true}]) of 37 | _Result -> 38 | ok 39 | catch 40 | error:badarg -> 41 | ?INT_LOG(warning, "Table ~p already exists", [?TBL]) 42 | end, 43 | new_sink(?DEFAULT_SINK), 44 | %% Need to be able to find the `lager_handler_watcher' for all handlers 45 | ets:insert_new(?TBL, {{?GLOBAL, handlers}, []}), 46 | ok. 47 | 48 | new_sink(Sink) -> 49 | %% use insert_new here so that if we're in an appup we don't mess anything up 50 | %% 51 | %% until lager is completely started, allow all messages to go through 52 | ets:insert_new(?TBL, {{Sink, loglevel}, {element(2, lager_util:config_to_mask(debug)), []}}). 53 | 54 | global_get(Key) -> 55 | global_get(Key, undefined). 56 | 57 | global_get(Key, Default) -> 58 | get({?GLOBAL, Key}, Default). 59 | 60 | global_set(Key, Value) -> 61 | set({?GLOBAL, Key}, Value). 62 | 63 | 64 | get({_Sink, _Key}=FullKey) -> 65 | get(FullKey, undefined); 66 | get(Key) -> 67 | get({?DEFAULT_SINK, Key}, undefined). 68 | 69 | get({Sink, Key}, Default) -> 70 | try 71 | case ets:lookup(?TBL, {Sink, Key}) of 72 | [] -> 73 | Default; 74 | [{{Sink, Key}, Res}] -> 75 | Res 76 | end 77 | catch 78 | _:_ -> 79 | Default 80 | end; 81 | get(Key, Default) -> 82 | get({?DEFAULT_SINK, Key}, Default). 83 | 84 | set({Sink, Key}, Value) -> 85 | ets:insert(?TBL, {{Sink, Key}, Value}); 86 | set(Key, Value) -> 87 | set({?DEFAULT_SINK, Key}, Value). 88 | -------------------------------------------------------------------------------- /src/lager_console_backend.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011-2012, 2014 Basho Technologies, Inc. All Rights Reserved. 2 | %% 3 | %% This file is provided to you under the Apache License, 4 | %% Version 2.0 (the "License"); you may not use this file 5 | %% except in compliance with the License. You may obtain 6 | %% a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, 11 | %% software distributed under the License is distributed on an 12 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | %% KIND, either express or implied. See the License for the 14 | %% specific language governing permissions and limitations 15 | %% under the License. 16 | 17 | %% @doc Console backend for lager. Configured with a single option, the loglevel 18 | %% desired. 19 | 20 | -module(lager_console_backend). 21 | 22 | -behaviour(gen_event). 23 | 24 | -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, 25 | code_change/3]). 26 | 27 | -record(state, {level :: {'mask', integer()}, 28 | formatter :: atom(), 29 | format_config :: any(), 30 | colors=[] :: list()}). 31 | 32 | -ifdef(TEST). 33 | -include_lib("eunit/include/eunit.hrl"). 34 | -compile([{parse_transform, lager_transform}]). 35 | -endif. 36 | 37 | -include("lager.hrl"). 38 | -define(TERSE_FORMAT,[time, " ", color, "[", severity,"] ", message]). 39 | 40 | %% @private 41 | init([Level]) when is_atom(Level) -> 42 | init(Level); 43 | init([Level, true]) -> % for backwards compatibility 44 | init([Level,{lager_default_formatter,[{eol, eol()}]}]); 45 | init([Level,false]) -> % for backwards compatibility 46 | init([Level,{lager_default_formatter,?TERSE_FORMAT ++ [eol()]}]); 47 | init([Level,{Formatter,FormatterConfig}]) when is_atom(Formatter) -> 48 | Colors = case application:get_env(lager, colored) of 49 | {ok, true} -> 50 | {ok, LagerColors} = application:get_env(lager, colors), 51 | LagerColors; 52 | _ -> [] 53 | end, 54 | 55 | try {is_new_style_console_available(), lager_util:config_to_mask(Level)} of 56 | {false, _} -> 57 | Msg = "Lager's console backend is incompatible with the 'old' shell, not enabling it", 58 | %% be as noisy as possible, log to every possible place 59 | try 60 | alarm_handler:set_alarm({?MODULE, "WARNING: " ++ Msg}) 61 | catch 62 | _:_ -> 63 | error_logger:warning_msg(Msg ++ "~n") 64 | end, 65 | io:format("WARNING: " ++ Msg ++ "~n"), 66 | ?INT_LOG(warning, Msg, []), 67 | {error, {fatal, old_shell}}; 68 | {true, Levels} -> 69 | {ok, #state{level=Levels, 70 | formatter=Formatter, 71 | format_config=FormatterConfig, 72 | colors=Colors}} 73 | catch 74 | _:_ -> 75 | {error, {fatal, bad_log_level}} 76 | end; 77 | init(Level) -> 78 | init([Level,{lager_default_formatter,?TERSE_FORMAT ++ [eol()]}]). 79 | 80 | %% @private 81 | handle_call(get_loglevel, #state{level=Level} = State) -> 82 | {ok, Level, State}; 83 | handle_call({set_loglevel, Level}, State) -> 84 | try lager_util:config_to_mask(Level) of 85 | Levels -> 86 | {ok, ok, State#state{level=Levels}} 87 | catch 88 | _:_ -> 89 | {ok, {error, bad_log_level}, State} 90 | end; 91 | handle_call(_Request, State) -> 92 | {ok, ok, State}. 93 | 94 | %% @private 95 | handle_event({log, Message}, 96 | #state{level=L,formatter=Formatter,format_config=FormatConfig,colors=Colors} = State) -> 97 | case lager_util:is_loggable(Message, L, ?MODULE) of 98 | true -> 99 | io:put_chars(user, Formatter:format(Message,FormatConfig,Colors)), 100 | {ok, State}; 101 | false -> 102 | {ok, State} 103 | end; 104 | handle_event(_Event, State) -> 105 | {ok, State}. 106 | 107 | %% @private 108 | handle_info(_Info, State) -> 109 | {ok, State}. 110 | 111 | %% @private 112 | terminate(_Reason, _State) -> 113 | ok. 114 | 115 | %% @private 116 | code_change(_OldVsn, State, _Extra) -> 117 | {ok, State}. 118 | 119 | eol() -> 120 | case application:get_env(lager, colored) of 121 | {ok, true} -> 122 | "\e[0m\r\n"; 123 | _ -> 124 | "\r\n" 125 | end. 126 | 127 | -ifdef(TEST). 128 | is_new_style_console_available() -> 129 | true. 130 | -else. 131 | is_new_style_console_available() -> 132 | %% Criteria: 133 | %% 1. If the user has specified '-noshell' on the command line, 134 | %% then we will pretend that the new-style console is available. 135 | %% If there is no shell at all, then we don't have to worry 136 | %% about log events being blocked by the old-style shell. 137 | %% 2. Windows doesn't support the new shell, so all windows users 138 | %% have is the oldshell. 139 | %% 3. If the user_drv process is registered, all is OK. 140 | %% 'user_drv' is a registered proc name used by the "new" 141 | %% console driver. 142 | init:get_argument(noshell) /= error orelse 143 | element(1, os:type()) /= win32 orelse 144 | is_pid(whereis(user_drv)). 145 | -endif. 146 | 147 | -ifdef(TEST). 148 | console_log_test_() -> 149 | %% tiny recursive fun that pretends to be a group leader 150 | F = fun(Self) -> 151 | fun() -> 152 | YComb = fun(Fun) -> 153 | receive 154 | {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} = Y -> 155 | From ! {io_reply, ReplyAs, ok}, 156 | Self ! Y, 157 | Fun(Fun); 158 | Other -> 159 | ?debugFmt("unexpected message ~p~n", [Other]), 160 | Self ! Other 161 | end 162 | end, 163 | YComb(YComb) 164 | end 165 | end, 166 | {foreach, 167 | fun() -> 168 | error_logger:tty(false), 169 | application:load(lager), 170 | application:set_env(lager, handlers, []), 171 | application:set_env(lager, error_logger_redirect, false), 172 | lager:start(), 173 | whereis(user) 174 | end, 175 | fun(User) -> 176 | unregister(user), 177 | register(user, User), 178 | application:stop(lager), 179 | application:stop(goldrush), 180 | error_logger:tty(true) 181 | end, 182 | [ 183 | {"regular console logging", 184 | fun() -> 185 | Pid = spawn(F(self())), 186 | unregister(user), 187 | register(user, Pid), 188 | erlang:group_leader(Pid, whereis(lager_event)), 189 | gen_event:add_handler(lager_event, lager_console_backend, info), 190 | lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), 191 | lager:log(info, self(), "Test message"), 192 | receive 193 | {io_request, From, ReplyAs, {put_chars, unicode, Msg}} -> 194 | From ! {io_reply, ReplyAs, ok}, 195 | TestMsg = "Test message" ++ eol(), 196 | ?assertMatch([_, "[info]", TestMsg], re:split(Msg, " ", [{return, list}, {parts, 3}])) 197 | after 198 | 500 -> 199 | ?assert(false) 200 | end 201 | end 202 | }, 203 | {"verbose console logging", 204 | fun() -> 205 | Pid = spawn(F(self())), 206 | unregister(user), 207 | register(user, Pid), 208 | erlang:group_leader(Pid, whereis(lager_event)), 209 | gen_event:add_handler(lager_event, lager_console_backend, [info, true]), 210 | lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), 211 | lager:info("Test message"), 212 | PidStr = pid_to_list(self()), 213 | receive 214 | {io_request, _, _, {put_chars, unicode, Msg}} -> 215 | TestMsg = "Test message" ++ eol(), 216 | ?assertMatch([_, _, "[info]", PidStr, _, TestMsg], re:split(Msg, "[ @]", [{return, list}, {parts, 6}])) 217 | after 218 | 500 -> 219 | ?assert(false) 220 | end 221 | end 222 | }, 223 | {"custom format console logging", 224 | fun() -> 225 | Pid = spawn(F(self())), 226 | unregister(user), 227 | register(user, Pid), 228 | erlang:group_leader(Pid, whereis(lager_event)), 229 | gen_event:add_handler(lager_event, lager_console_backend, 230 | [info, {lager_default_formatter, [date,"#",time,"#",severity,"#",node,"#",pid,"#", 231 | module,"#",function,"#",file,"#",line,"#",message,"\r\n"]}]), 232 | lager_config:set({lager_event, loglevel}, {?INFO, []}), 233 | lager:info("Test message"), 234 | PidStr = pid_to_list(self()), 235 | NodeStr = atom_to_list(node()), 236 | ModuleStr = atom_to_list(?MODULE), 237 | receive 238 | {io_request, _, _, {put_chars, unicode, Msg}} -> 239 | TestMsg = "Test message" ++ eol(), 240 | ?assertMatch([_, _, "info", NodeStr, PidStr, ModuleStr, _, _, _, TestMsg], 241 | re:split(Msg, "#", [{return, list}, {parts, 10}])) 242 | after 243 | 500 -> 244 | ?assert(false) 245 | end 246 | end 247 | }, 248 | {"tracing should work", 249 | fun() -> 250 | Pid = spawn(F(self())), 251 | unregister(user), 252 | register(user, Pid), 253 | gen_event:add_handler(lager_event, lager_console_backend, info), 254 | erlang:group_leader(Pid, whereis(lager_event)), 255 | lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), 256 | lager:debug("Test message"), 257 | receive 258 | {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} -> 259 | From ! {io_reply, ReplyAs, ok}, 260 | ?assert(false) 261 | after 262 | 500 -> 263 | ?assert(true) 264 | end, 265 | {ok, _} = lager:trace_console([{module, ?MODULE}]), 266 | lager:debug("Test message"), 267 | receive 268 | {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} -> 269 | From1 ! {io_reply, ReplyAs1, ok}, 270 | TestMsg = "Test message" ++ eol(), 271 | ?assertMatch([_, "[debug]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}])) 272 | after 273 | 500 -> 274 | ?assert(false) 275 | end 276 | end 277 | }, 278 | {"tracing doesn't duplicate messages", 279 | fun() -> 280 | Pid = spawn(F(self())), 281 | unregister(user), 282 | register(user, Pid), 283 | gen_event:add_handler(lager_event, lager_console_backend, info), 284 | lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), 285 | erlang:group_leader(Pid, whereis(lager_event)), 286 | lager:debug("Test message"), 287 | receive 288 | {io_request, From, ReplyAs, {put_chars, unicode, _Msg}} -> 289 | From ! {io_reply, ReplyAs, ok}, 290 | ?assert(false) 291 | after 292 | 500 -> 293 | ?assert(true) 294 | end, 295 | {ok, _} = lager:trace_console([{module, ?MODULE}]), 296 | lager:error("Test message"), 297 | receive 298 | {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} -> 299 | From1 ! {io_reply, ReplyAs1, ok}, 300 | TestMsg = "Test message" ++ eol(), 301 | ?assertMatch([_, "[error]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}])) 302 | after 303 | 1000 -> 304 | ?assert(false) 305 | end, 306 | %% make sure this event wasn't duplicated 307 | receive 308 | {io_request, From2, ReplyAs2, {put_chars, unicode, _Msg2}} -> 309 | From2 ! {io_reply, ReplyAs2, ok}, 310 | ?assert(false) 311 | after 312 | 500 -> 313 | ?assert(true) 314 | end 315 | end 316 | }, 317 | {"blacklisting a loglevel works", 318 | fun() -> 319 | Pid = spawn(F(self())), 320 | unregister(user), 321 | register(user, Pid), 322 | gen_event:add_handler(lager_event, lager_console_backend, info), 323 | lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), 324 | lager:set_loglevel(lager_console_backend, '!=info'), 325 | erlang:group_leader(Pid, whereis(lager_event)), 326 | lager:debug("Test message"), 327 | receive 328 | {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} -> 329 | From1 ! {io_reply, ReplyAs1, ok}, 330 | TestMsg = "Test message" ++ eol(), 331 | ?assertMatch([_, "[debug]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}])) 332 | after 333 | 1000 -> 334 | ?assert(false) 335 | end, 336 | %% info is blacklisted 337 | lager:info("Test message"), 338 | receive 339 | {io_request, From2, ReplyAs2, {put_chars, unicode, _Msg2}} -> 340 | From2 ! {io_reply, ReplyAs2, ok}, 341 | ?assert(false) 342 | after 343 | 500 -> 344 | ?assert(true) 345 | end 346 | end 347 | }, 348 | {"whitelisting a loglevel works", 349 | fun() -> 350 | Pid = spawn(F(self())), 351 | unregister(user), 352 | register(user, Pid), 353 | gen_event:add_handler(lager_event, lager_console_backend, info), 354 | lager_config:set({lager_event, loglevel}, {element(2, lager_util:config_to_mask(info)), []}), 355 | lager:set_loglevel(lager_console_backend, '=debug'), 356 | erlang:group_leader(Pid, whereis(lager_event)), 357 | lager:debug("Test message"), 358 | receive 359 | {io_request, From1, ReplyAs1, {put_chars, unicode, Msg1}} -> 360 | From1 ! {io_reply, ReplyAs1, ok}, 361 | TestMsg = "Test message" ++ eol(), 362 | ?assertMatch([_, "[debug]", TestMsg], re:split(Msg1, " ", [{return, list}, {parts, 3}])) 363 | after 364 | 1000 -> 365 | ?assert(false) 366 | end, 367 | %% info is blacklisted 368 | lager:error("Test message"), 369 | receive 370 | {io_request, From2, ReplyAs2, {put_chars, unicode, _Msg2}} -> 371 | From2 ! {io_reply, ReplyAs2, ok}, 372 | ?assert(false) 373 | after 374 | 500 -> 375 | ?assert(true) 376 | end 377 | end 378 | } 379 | ] 380 | }. 381 | 382 | set_loglevel_test_() -> 383 | {foreach, 384 | fun() -> 385 | error_logger:tty(false), 386 | application:load(lager), 387 | application:set_env(lager, handlers, [{lager_console_backend, info}]), 388 | application:set_env(lager, error_logger_redirect, false), 389 | lager:start() 390 | end, 391 | fun(_) -> 392 | application:stop(lager), 393 | application:stop(goldrush), 394 | error_logger:tty(true) 395 | end, 396 | [ 397 | {"Get/set loglevel test", 398 | fun() -> 399 | ?assertEqual(info, lager:get_loglevel(lager_console_backend)), 400 | lager:set_loglevel(lager_console_backend, debug), 401 | ?assertEqual(debug, lager:get_loglevel(lager_console_backend)), 402 | lager:set_loglevel(lager_console_backend, '!=debug'), 403 | ?assertEqual(info, lager:get_loglevel(lager_console_backend)), 404 | lager:set_loglevel(lager_console_backend, '!=info'), 405 | ?assertEqual(debug, lager:get_loglevel(lager_console_backend)), 406 | ok 407 | end 408 | }, 409 | {"Get/set invalid loglevel test", 410 | fun() -> 411 | ?assertEqual(info, lager:get_loglevel(lager_console_backend)), 412 | ?assertEqual({error, bad_log_level}, 413 | lager:set_loglevel(lager_console_backend, fatfinger)), 414 | ?assertEqual(info, lager:get_loglevel(lager_console_backend)) 415 | end 416 | } 417 | 418 | ] 419 | }. 420 | 421 | -endif. 422 | -------------------------------------------------------------------------------- /src/lager_crash_log.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. 2 | %% 3 | %% This file is provided to you under the Apache License, 4 | %% Version 2.0 (the "License"); you may not use this file 5 | %% except in compliance with the License. You may obtain 6 | %% a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, 11 | %% software distributed under the License is distributed on an 12 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | %% KIND, either express or implied. See the License for the 14 | %% specific language governing permissions and limitations 15 | %% under the License. 16 | 17 | %% @doc Lager crash log writer. This module implements a gen_server which writes 18 | %% error_logger error messages out to a file in their original format. The 19 | %% location to which it logs is configured by the application var `crash_log'. 20 | %% Omitting this variable disables crash logging. Crash logs are printed safely 21 | %% using trunc_io via code mostly lifted from riak_err. 22 | %% 23 | %% The `crash_log_msg_size' application var is used to specify the maximum 24 | %% size of any message to be logged. `crash_log_size' is used to specify the 25 | %% maximum size of the crash log before it will be rotated (0 will disable). 26 | %% Time based rotation is configurable via `crash_log_date', the syntax is 27 | %% documented in the README. To control the number of rotated files to be 28 | %% retained, use `crash_log_count'. 29 | 30 | -module(lager_crash_log). 31 | 32 | -include("lager.hrl"). 33 | 34 | -behaviour(gen_server). 35 | 36 | -ifdef(TEST). 37 | -include_lib("eunit/include/eunit.hrl"). 38 | -include_lib("kernel/include/file.hrl"). 39 | -endif. 40 | 41 | %% callbacks 42 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, 43 | code_change/3]). 44 | 45 | -export([start_link/5, start/5]). 46 | 47 | -record(state, { 48 | name :: string(), 49 | fd :: pid() | undefined, 50 | inode :: integer() | undefined, 51 | fmtmaxbytes :: integer(), 52 | size :: integer(), 53 | date :: undefined | string(), 54 | count :: integer(), 55 | flap=false :: boolean() 56 | }). 57 | 58 | %% @private 59 | start_link(Filename, MaxBytes, Size, Date, Count) -> 60 | gen_server:start_link({local, ?MODULE}, ?MODULE, [Filename, MaxBytes, 61 | Size, Date, Count], []). 62 | 63 | %% @private 64 | start(Filename, MaxBytes, Size, Date, Count) -> 65 | gen_server:start({local, ?MODULE}, ?MODULE, [Filename, MaxBytes, Size, 66 | Date, Count], []). 67 | 68 | %% @private 69 | init([RelFilename, MaxBytes, Size, Date, Count]) -> 70 | Filename = lager_util:expand_path(RelFilename), 71 | case lager_util:open_logfile(Filename, false) of 72 | {ok, {FD, Inode, _}} -> 73 | schedule_rotation(Date), 74 | {ok, #state{name=Filename, fd=FD, inode=Inode, 75 | fmtmaxbytes=MaxBytes, size=Size, count=Count, date=Date}}; 76 | {error, Reason} -> 77 | ?INT_LOG(error, "Failed to open crash log file ~s with error: ~s", 78 | [Filename, file:format_error(Reason)]), 79 | {ok, #state{name=Filename, fmtmaxbytes=MaxBytes, flap=true, 80 | size=Size, count=Count, date=Date}} 81 | end. 82 | 83 | %% @private 84 | handle_call({log, _} = Log, _From, State) -> 85 | {Reply, NewState} = do_log(Log, State), 86 | {reply, Reply, NewState}; 87 | handle_call(_Call, _From, State) -> 88 | {reply, ok, State}. 89 | 90 | %% @private 91 | handle_cast({log, _} = Log, State) -> 92 | {_, NewState} = do_log(Log, State), 93 | {noreply, NewState}; 94 | handle_cast(_Request, State) -> 95 | {noreply, State}. 96 | 97 | %% @private 98 | handle_info(rotate, #state{name=Name, count=Count, date=Date} = State) -> 99 | _ = lager_util:rotate_logfile(Name, Count), 100 | schedule_rotation(Date), 101 | {noreply, State}; 102 | handle_info(_Info, State) -> 103 | {noreply, State}. 104 | 105 | %% @private 106 | terminate(_Reason, _State) -> 107 | ok. 108 | 109 | %% @private 110 | code_change(_OldVsn, State, _Extra) -> 111 | {ok, State}. 112 | 113 | schedule_rotation(undefined) -> 114 | ok; 115 | schedule_rotation(Date) -> 116 | erlang:send_after(lager_util:calculate_next_rotation(Date) * 1000, self(), rotate), 117 | ok. 118 | 119 | %% ===== Begin code lifted from riak_err ===== 120 | 121 | -spec limited_fmt(string(), list(), integer()) -> iolist(). 122 | %% @doc Format Fmt and Args similar to what io_lib:format/2 does but with 123 | %% limits on how large the formatted string may be. 124 | %% 125 | %% If the Args list's size is larger than TermMaxSize, then the 126 | %% formatting is done by trunc_io:print/2, where FmtMaxBytes is used 127 | %% to limit the formatted string's size. 128 | 129 | limited_fmt(Fmt, Args, FmtMaxBytes) -> 130 | lager:safe_format(Fmt, Args, FmtMaxBytes). 131 | 132 | limited_str(Term, FmtMaxBytes) -> 133 | {Str, _} = lager_trunc_io:print(Term, FmtMaxBytes), 134 | Str. 135 | 136 | other_node_suffix(Pid) when node(Pid) =/= node() -> 137 | "** at node " ++ atom_to_list(node(Pid)) ++ " **\n"; 138 | other_node_suffix(_) -> 139 | "". 140 | 141 | perhaps_a_sasl_report(error_report, {Pid, Type, Report}, FmtMaxBytes) -> 142 | case lager_stdlib:is_my_error_report(Type) of 143 | true -> 144 | {sasl_type_to_report_head(Type), Pid, 145 | sasl_limited_str(Type, Report, FmtMaxBytes), true}; 146 | false -> 147 | {ignore, ignore, ignore, false} 148 | end; 149 | %perhaps_a_sasl_report(info_report, {Pid, Type, Report}, FmtMaxBytes) -> 150 | %case lager_stdlib:is_my_info_report(Type) of 151 | %true -> 152 | %{sasl_type_to_report_head(Type), Pid, 153 | %sasl_limited_str(Type, Report, FmtMaxBytes), false}; 154 | %false -> 155 | %{ignore, ignore, ignore, false} 156 | %end; 157 | perhaps_a_sasl_report(_, _, _) -> 158 | {ignore, ignore, ignore, false}. 159 | 160 | sasl_type_to_report_head(supervisor_report) -> 161 | "SUPERVISOR REPORT"; 162 | sasl_type_to_report_head(crash_report) -> 163 | "CRASH REPORT"; 164 | sasl_type_to_report_head(progress) -> 165 | "PROGRESS REPORT". 166 | 167 | sasl_limited_str(supervisor_report, Report, FmtMaxBytes) -> 168 | Name = lager_stdlib:sup_get(supervisor, Report), 169 | Context = lager_stdlib:sup_get(errorContext, Report), 170 | Reason = lager_stdlib:sup_get(reason, Report), 171 | Offender = lager_stdlib:sup_get(offender, Report), 172 | FmtString = " Supervisor: ~p~n Context: ~p~n Reason: " 173 | "~s~n Offender: ~s~n~n", 174 | {ReasonStr, _} = lager_trunc_io:print(Reason, FmtMaxBytes), 175 | {OffenderStr, _} = lager_trunc_io:print(Offender, FmtMaxBytes), 176 | io_lib:format(FmtString, [Name, Context, ReasonStr, OffenderStr]); 177 | sasl_limited_str(progress, Report, FmtMaxBytes) -> 178 | [begin 179 | {Str, _} = lager_trunc_io:print(Data, FmtMaxBytes), 180 | io_lib:format(" ~16w: ~s~n", [Tag, Str]) 181 | end || {Tag, Data} <- Report]; 182 | sasl_limited_str(crash_report, Report, FmtMaxBytes) -> 183 | lager_stdlib:proc_lib_format(Report, FmtMaxBytes). 184 | 185 | do_log({log, Event}, #state{name=Name, fd=FD, inode=Inode, flap=Flap, 186 | fmtmaxbytes=FmtMaxBytes, size=RotSize, count=Count} = State) -> 187 | %% borrowed from riak_err 188 | {ReportStr, Pid, MsgStr, _ErrorP} = case Event of 189 | {error, _GL, {Pid1, Fmt, Args}} -> 190 | {"ERROR REPORT", Pid1, limited_fmt(Fmt, Args, FmtMaxBytes), true}; 191 | {error_report, _GL, {Pid1, std_error, Rep}} -> 192 | {"ERROR REPORT", Pid1, limited_str(Rep, FmtMaxBytes) ++ "\n", true}; 193 | {error_report, _GL, Other} -> 194 | perhaps_a_sasl_report(error_report, Other, FmtMaxBytes); 195 | _ -> 196 | {ignore, ignore, ignore, false} 197 | end, 198 | if ReportStr == ignore -> 199 | {ok, State}; 200 | true -> 201 | case lager_util:ensure_logfile(Name, FD, Inode, false) of 202 | {ok, {_, _, Size}} when RotSize /= 0, Size > RotSize -> 203 | _ = lager_util:rotate_logfile(Name, Count), 204 | handle_cast({log, Event}, State); 205 | {ok, {NewFD, NewInode, _Size}} -> 206 | {Date, TS} = lager_util:format_time( 207 | lager_stdlib:maybe_utc(erlang:localtime())), 208 | Time = [Date, " ", TS," =", ReportStr, "====\n"], 209 | NodeSuffix = other_node_suffix(Pid), 210 | Msg = io_lib:format("~s~s~s", [Time, MsgStr, NodeSuffix]), 211 | case file:write(NewFD, unicode:characters_to_binary(Msg)) of 212 | {error, Reason} when Flap == false -> 213 | ?INT_LOG(error, "Failed to write log message to file ~s: ~s", 214 | [Name, file:format_error(Reason)]), 215 | {ok, State#state{fd=NewFD, inode=NewInode, flap=true}}; 216 | ok -> 217 | {ok, State#state{fd=NewFD, inode=NewInode, flap=false}}; 218 | _ -> 219 | {ok, State#state{fd=NewFD, inode=NewInode}} 220 | end; 221 | {error, Reason} -> 222 | case Flap of 223 | true -> 224 | {ok, State}; 225 | _ -> 226 | ?INT_LOG(error, "Failed to reopen crash log ~s with error: ~s", 227 | [Name, file:format_error(Reason)]), 228 | {ok, State#state{flap=true}} 229 | end 230 | end 231 | end. 232 | 233 | 234 | -ifdef(TEST). 235 | 236 | filesystem_test_() -> 237 | {foreach, 238 | fun() -> 239 | file:write_file("crash_test.log", ""), 240 | error_logger:tty(false), 241 | application:load(lager), 242 | application:set_env(lager, handlers, [{lager_test_backend, info}]), 243 | application:set_env(lager, error_logger_redirect, true), 244 | application:unset_env(lager, crash_log), 245 | lager:start(), 246 | timer:sleep(100), 247 | lager_test_backend:flush() 248 | end, 249 | fun(_) -> 250 | case whereis(lager_crash_log) of 251 | P when is_pid(P) -> 252 | exit(P, kill); 253 | _ -> ok 254 | end, 255 | file:delete("crash_test.log"), 256 | application:stop(lager), 257 | application:stop(goldrush), 258 | error_logger:tty(true) 259 | end, 260 | [ 261 | {"under normal circumstances, file should be opened", 262 | fun() -> 263 | {ok, _} = ?MODULE:start_link("crash_test.log", 65535, 0, undefined, 0), 264 | _ = gen_event:which_handlers(error_logger), 265 | sync_error_logger:error_msg("Test message\n"), 266 | {ok, Bin} = file:read_file("crash_test.log"), 267 | ?assertMatch([_, "Test message\n"], re:split(Bin, "\n", [{return, list}, {parts, 2}])) 268 | end 269 | }, 270 | {"file can't be opened on startup triggers an error message", 271 | fun() -> 272 | {ok, FInfo} = file:read_file_info("crash_test.log"), 273 | file:write_file_info("crash_test.log", FInfo#file_info{mode = 0}), 274 | {ok, _} = ?MODULE:start_link("crash_test.log", 65535, 0, undefined, 0), 275 | ?assertEqual(1, lager_test_backend:count()), 276 | {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(), 277 | ?assertEqual("Failed to open crash log file crash_test.log with error: permission denied", lists:flatten(Message)) 278 | end 279 | }, 280 | {"file that becomes unavailable at runtime should trigger an error message", 281 | fun() -> 282 | {ok, _} = ?MODULE:start_link("crash_test.log", 65535, 0, undefined, 0), 283 | ?assertEqual(0, lager_test_backend:count()), 284 | sync_error_logger:error_msg("Test message\n"), 285 | _ = gen_event:which_handlers(error_logger), 286 | ?assertEqual(1, lager_test_backend:count()), 287 | file:delete("crash_test.log"), 288 | file:write_file("crash_test.log", ""), 289 | {ok, FInfo} = file:read_file_info("crash_test.log"), 290 | file:write_file_info("crash_test.log", FInfo#file_info{mode = 0}), 291 | sync_error_logger:error_msg("Test message\n"), 292 | _ = gen_event:which_handlers(error_logger), 293 | ?assertEqual(3, lager_test_backend:count()), 294 | lager_test_backend:pop(), 295 | {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(), 296 | ?assertEqual("Failed to reopen crash log crash_test.log with error: permission denied", lists:flatten(Message)) 297 | end 298 | }, 299 | {"unavailable files that are fixed at runtime should start having log messages written", 300 | fun() -> 301 | {ok, FInfo} = file:read_file_info("crash_test.log"), 302 | OldPerms = FInfo#file_info.mode, 303 | file:write_file_info("crash_test.log", FInfo#file_info{mode = 0}), 304 | {ok, _} = ?MODULE:start_link("crash_test.log", 65535, 0, undefined, 0), 305 | ?assertEqual(1, lager_test_backend:count()), 306 | {_Level, _Time, Message,_Metadata} = lager_test_backend:pop(), 307 | ?assertEqual("Failed to open crash log file crash_test.log with error: permission denied", lists:flatten(Message)), 308 | file:write_file_info("crash_test.log", FInfo#file_info{mode = OldPerms}), 309 | sync_error_logger:error_msg("Test message~n"), 310 | _ = gen_event:which_handlers(error_logger), 311 | {ok, Bin} = file:read_file("crash_test.log"), 312 | ?assertMatch([_, "Test message\n"], re:split(Bin, "\n", [{return, list}, {parts, 2}])) 313 | end 314 | }, 315 | {"external logfile rotation/deletion should be handled", 316 | fun() -> 317 | {ok, _} = ?MODULE:start_link("crash_test.log", 65535, 0, undefined, 0), 318 | ?assertEqual(0, lager_test_backend:count()), 319 | sync_error_logger:error_msg("Test message~n"), 320 | _ = gen_event:which_handlers(error_logger), 321 | {ok, Bin} = file:read_file("crash_test.log"), 322 | ?assertMatch([_, "Test message\n"], re:split(Bin, "\n", [{return, list}, {parts, 2}])), 323 | file:delete("crash_test.log"), 324 | file:write_file("crash_test.log", ""), 325 | sync_error_logger:error_msg("Test message1~n"), 326 | _ = gen_event:which_handlers(error_logger), 327 | {ok, Bin1} = file:read_file("crash_test.log"), 328 | ?assertMatch([_, "Test message1\n"], re:split(Bin1, "\n", [{return, list}, {parts, 2}])), 329 | file:rename("crash_test.log", "crash_test.log.0"), 330 | sync_error_logger:error_msg("Test message2~n"), 331 | _ = gen_event:which_handlers(error_logger), 332 | {ok, Bin2} = file:read_file("crash_test.log"), 333 | ?assertMatch([_, "Test message2\n"], re:split(Bin2, "\n", [{return, list}, {parts, 2}])) 334 | end 335 | } 336 | ] 337 | }. 338 | 339 | -endif. 340 | 341 | -------------------------------------------------------------------------------- /src/lager_default_formatter.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. 2 | %% 3 | %% This file is provided to you under the Apache License, 4 | %% Version 2.0 (the "License"); you may not use this file 5 | %% except in compliance with the License. You may obtain 6 | %% a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, 11 | %% software distributed under the License is distributed on an 12 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | %% KIND, either express or implied. See the License for the 14 | %% specific language governing permissions and limitations 15 | %% under the License. 16 | 17 | -module(lager_default_formatter). 18 | 19 | %% 20 | %% Include files 21 | %% 22 | -include("lager.hrl"). 23 | -ifdef(TEST). 24 | -include_lib("eunit/include/eunit.hrl"). 25 | -endif. 26 | 27 | %% 28 | %% Exported Functions 29 | %% 30 | -export([format/2, format/3]). 31 | 32 | %% 33 | %% API Functions 34 | %% 35 | 36 | %% @doc Provides a generic, default formatting for log messages using a semi-iolist as configuration. Any iolist allowed 37 | %% elements in the configuration are printed verbatim. Atoms in the configuration are treated as metadata properties 38 | %% and extracted from the log message. Optionally, a tuple of {atom(),semi-iolist()} can be used. The atom will look 39 | %% up the property, but if not found it will use the semi-iolist() instead. These fallbacks can be similarly nested 40 | %% or refer to other properties, if desired. You can also use a {atom, semi-iolist(), semi-iolist()} formatter, which 41 | %% acts like a ternary operator's true/false branches. 42 | %% 43 | %% The metadata properties date,time, message, severity, and sev will always exist. 44 | %% The properties pid, file, line, module, and function will always exist if the parser transform is used. 45 | %% 46 | %% Example: 47 | %% 48 | %% `["Foo"]' -> "Foo", regardless of message content. 49 | %% 50 | %% `[message]' -> The content of the logged message, alone. 51 | %% 52 | %% `[{pid,"Unknown Pid"}]' -> "?.?.?" if pid is in the metadata, "Unknown Pid" if not. 53 | %% 54 | %% `[{pid, ["My pid is ", pid], ["Unknown Pid"]}]' -> if pid is in the metada print "My pid is ?.?.?", otherwise print "Unknown Pid" 55 | %% @end 56 | -spec format(lager_msg:lager_msg(),list(),list()) -> any(). 57 | format(Msg,[], Colors) -> 58 | format(Msg, [{eol, "\n"}], Colors); 59 | format(Msg,[{eol, EOL}], Colors) -> 60 | format(Msg, 61 | [date, " ", time, " ", color, "[", severity, "] ", 62 | {pid, ""}, 63 | {module, [ 64 | {pid, ["@"], ""}, 65 | module, 66 | {function, [":", function], ""}, 67 | {line, [":",line], ""}], ""}, 68 | " ", message, EOL], Colors); 69 | format(Message,Config,Colors) -> 70 | [ case V of 71 | color -> output_color(Message,Colors); 72 | _ -> output(V,Message) 73 | end || V <- Config ]. 74 | 75 | -spec format(lager_msg:lager_msg(),list()) -> any(). 76 | format(Msg, Config) -> 77 | format(Msg, Config, []). 78 | 79 | -spec output(term(),lager_msg:lager_msg()) -> iolist(). 80 | output(message,Msg) -> lager_msg:message(Msg); 81 | output(date,Msg) -> 82 | {D, _T} = lager_msg:datetime(Msg), 83 | D; 84 | output(time,Msg) -> 85 | {_D, T} = lager_msg:datetime(Msg), 86 | T; 87 | output(severity,Msg) -> 88 | atom_to_list(lager_msg:severity(Msg)); 89 | output(blank,_Msg) -> 90 | output({blank," "},_Msg); 91 | output({blank,Fill},_Msg) -> 92 | Fill; 93 | output(sev,Msg) -> 94 | %% Write brief acronym for the severity level (e.g. debug -> $D) 95 | [lager_util:level_to_chr(lager_msg:severity(Msg))]; 96 | output(metadata, Msg) -> 97 | output({metadata, "=", " "}, Msg); 98 | output({metadata, IntSep, FieldSep}, Msg) -> 99 | MD = lists:keysort(1, lager_msg:metadata(Msg)), 100 | string:join([io_lib:format("~s~s~p", [K, IntSep, V]) || {K, V} <- MD], FieldSep); 101 | output(Prop,Msg) when is_atom(Prop) -> 102 | Metadata = lager_msg:metadata(Msg), 103 | make_printable(get_metadata(Prop,Metadata,<<"Undefined">>)); 104 | output({Prop,Default},Msg) when is_atom(Prop) -> 105 | Metadata = lager_msg:metadata(Msg), 106 | make_printable(get_metadata(Prop,Metadata,output(Default,Msg))); 107 | output({Prop, Present, Absent}, Msg) when is_atom(Prop) -> 108 | %% sort of like a poor man's ternary operator 109 | Metadata = lager_msg:metadata(Msg), 110 | case get_metadata(Prop, Metadata) of 111 | undefined -> 112 | [ output(V, Msg) || V <- Absent]; 113 | _ -> 114 | [ output(V, Msg) || V <- Present] 115 | end; 116 | output({Prop, Present, Absent, Width}, Msg) when is_atom(Prop) -> 117 | %% sort of like a poor man's ternary operator 118 | Metadata = lager_msg:metadata(Msg), 119 | case get_metadata(Prop, Metadata) of 120 | undefined -> 121 | [ output(V, Msg, Width) || V <- Absent]; 122 | _ -> 123 | [ output(V, Msg, Width) || V <- Present] 124 | end; 125 | output(Other,_) -> make_printable(Other). 126 | 127 | output(message, Msg, _Width) -> lager_msg:message(Msg); 128 | output(date,Msg, _Width) -> 129 | {D, _T} = lager_msg:datetime(Msg), 130 | D; 131 | output(time, Msg, _Width) -> 132 | {_D, T} = lager_msg:datetime(Msg), 133 | T; 134 | output(severity, Msg, Width) -> 135 | make_printable(atom_to_list(lager_msg:severity(Msg)), Width); 136 | output(sev,Msg, _Width) -> 137 | %% Write brief acronym for the severity level (e.g. debug -> $D) 138 | [lager_util:level_to_chr(lager_msg:severity(Msg))]; 139 | output(blank,_Msg, _Width) -> 140 | output({blank, " "},_Msg, _Width); 141 | output({blank, Fill},_Msg, _Width) -> 142 | Fill; 143 | output(metadata, Msg, _Width) -> 144 | output({metadata, "=", " "}, Msg, _Width); 145 | output({metadata, IntSep, FieldSep}, Msg, _Width) -> 146 | MD = lists:keysort(1, lager_msg:metadata(Msg)), 147 | [string:join([io_lib:format("~s~s~p", [K, IntSep, V]) || {K, V} <- MD], FieldSep)]; 148 | 149 | output(Prop, Msg, Width) when is_atom(Prop) -> 150 | Metadata = lager_msg:metadata(Msg), 151 | make_printable(get_metadata(Prop,Metadata,<<"Undefined">>), Width); 152 | output({Prop,Default},Msg, Width) when is_atom(Prop) -> 153 | Metadata = lager_msg:metadata(Msg), 154 | make_printable(get_metadata(Prop,Metadata,output(Default,Msg)), Width); 155 | output(Other,_, Width) -> make_printable(Other, Width). 156 | 157 | output_color(_Msg,[]) -> []; 158 | output_color(Msg,Colors) -> 159 | Level = lager_msg:severity(Msg), 160 | case lists:keyfind(Level, 1, Colors) of 161 | {_, Color} -> Color; 162 | _ -> [] 163 | end. 164 | 165 | -spec make_printable(any()) -> iolist(). 166 | make_printable(A) when is_atom(A) -> atom_to_list(A); 167 | make_printable(P) when is_pid(P) -> pid_to_list(P); 168 | make_printable(L) when is_list(L) orelse is_binary(L) -> L; 169 | make_printable(Other) -> io_lib:format("~p",[Other]). 170 | 171 | make_printable(A,W) when is_integer(W)-> string:left(make_printable(A),W); 172 | make_printable(A,{Align,W}) when is_integer(W) -> 173 | case Align of 174 | left -> 175 | string:left(make_printable(A),W); 176 | centre -> 177 | string:centre(make_printable(A),W); 178 | right -> 179 | string:right(make_printable(A),W); 180 | _ -> 181 | string:left(make_printable(A),W) 182 | end; 183 | 184 | make_printable(A,_W) -> make_printable(A). 185 | 186 | get_metadata(Key, Metadata) -> 187 | get_metadata(Key, Metadata, undefined). 188 | 189 | get_metadata(Key, Metadata, Default) -> 190 | case lists:keyfind(Key, 1, Metadata) of 191 | false -> 192 | Default; 193 | {Key, Value} -> 194 | Value 195 | end. 196 | 197 | -ifdef(TEST). 198 | date_time_now() -> 199 | Now = os:timestamp(), 200 | {Date, Time} = lager_util:format_time(lager_util:maybe_utc(lager_util:localtime_ms(Now))), 201 | {Date, Time, Now}. 202 | 203 | basic_test_() -> 204 | {Date, Time, Now} = date_time_now(), 205 | [{"Default formatting test", 206 | ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] ", pid_to_list(self()), " Message\n"]), 207 | iolist_to_binary(format(lager_msg:new("Message", 208 | Now, 209 | error, 210 | [{pid, self()}], 211 | []), 212 | []))) 213 | }, 214 | {"Basic Formatting", 215 | ?_assertEqual(<<"Simplist Format">>, 216 | iolist_to_binary(format(lager_msg:new("Message", 217 | Now, 218 | error, 219 | [{pid, self()}], 220 | []), 221 | ["Simplist Format"]))) 222 | }, 223 | {"Default equivalent formatting test", 224 | ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] ", pid_to_list(self()), " Message\n"]), 225 | iolist_to_binary(format(lager_msg:new("Message", 226 | Now, 227 | error, 228 | [{pid, self()}], 229 | []), 230 | [date, " ", time," [",severity,"] ",pid, " ", message, "\n"] 231 | ))) 232 | }, 233 | {"Non existent metadata can default to string", 234 | ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] Fallback Message\n"]), 235 | iolist_to_binary(format(lager_msg:new("Message", 236 | Now, 237 | error, 238 | [{pid, self()}], 239 | []), 240 | [date, " ", time," [",severity,"] ",{does_not_exist,"Fallback"}, " ", message, "\n"] 241 | ))) 242 | }, 243 | {"Non existent metadata can default to other metadata", 244 | ?_assertEqual(iolist_to_binary([Date, " ", Time, " [error] Fallback Message\n"]), 245 | iolist_to_binary(format(lager_msg:new("Message", 246 | Now, 247 | error, 248 | [{pid, "Fallback"}], 249 | []), 250 | [date, " ", time," [",severity,"] ",{does_not_exist,pid}, " ", message, "\n"] 251 | ))) 252 | }, 253 | {"Non existent metadata can default to a string2", 254 | ?_assertEqual(iolist_to_binary(["Unknown Pid"]), 255 | iolist_to_binary(format(lager_msg:new("Message", 256 | Now, 257 | error, 258 | [], 259 | []), 260 | [{pid, ["My pid is ", pid], ["Unknown Pid"]}] 261 | ))) 262 | }, 263 | {"Metadata can have extra formatting", 264 | ?_assertEqual(iolist_to_binary(["My pid is hello"]), 265 | iolist_to_binary(format(lager_msg:new("Message", 266 | Now, 267 | error, 268 | [{pid, hello}], 269 | []), 270 | [{pid, ["My pid is ", pid], ["Unknown Pid"]}] 271 | ))) 272 | }, 273 | {"Metadata can have extra formatting1", 274 | ?_assertEqual(iolist_to_binary(["servername"]), 275 | iolist_to_binary(format(lager_msg:new("Message", 276 | Now, 277 | error, 278 | [{pid, hello}, {server, servername}], 279 | []), 280 | [{server,{pid, ["(", pid, ")"], ["(Unknown Server)"]}}] 281 | ))) 282 | }, 283 | {"Metadata can have extra formatting2", 284 | ?_assertEqual(iolist_to_binary(["(hello)"]), 285 | iolist_to_binary(format(lager_msg:new("Message", 286 | Now, 287 | error, 288 | [{pid, hello}], 289 | []), 290 | [{server,{pid, ["(", pid, ")"], ["(Unknown Server)"]}}] 291 | ))) 292 | }, 293 | {"Metadata can have extra formatting3", 294 | ?_assertEqual(iolist_to_binary(["(Unknown Server)"]), 295 | iolist_to_binary(format(lager_msg:new("Message", 296 | Now, 297 | error, 298 | [], 299 | []), 300 | [{server,{pid, ["(", pid, ")"], ["(Unknown Server)"]}}] 301 | ))) 302 | }, 303 | {"Metadata can be printed in its enterity", 304 | ?_assertEqual(iolist_to_binary(["bar=2 baz=3 foo=1"]), 305 | iolist_to_binary(format(lager_msg:new("Message", 306 | Now, 307 | error, 308 | [{foo, 1}, {bar, 2}, {baz, 3}], 309 | []), 310 | [metadata] 311 | ))) 312 | }, 313 | {"Metadata can be printed in its enterity with custom seperators", 314 | ?_assertEqual(iolist_to_binary(["bar->2, baz->3, foo->1"]), 315 | iolist_to_binary(format(lager_msg:new("Message", 316 | Now, 317 | error, 318 | [{foo, 1}, {bar, 2}, {baz, 3}], 319 | []), 320 | [{metadata, "->", ", "}] 321 | ))) 322 | }, 323 | {"Metadata can have extra formatting with width 1", 324 | ?_assertEqual(iolist_to_binary(["(hello )(hello )(hello)(hello)(hello)"]), 325 | iolist_to_binary(format(lager_msg:new("Message", 326 | Now, 327 | error, 328 | [{pid, hello}], 329 | []), 330 | ["(",{pid, [pid], "", 10},")", 331 | "(",{pid, [pid], "", {bad_align,10}},")", 332 | "(",{pid, [pid], "", bad10},")", 333 | "(",{pid, [pid], "", {right,bad20}},")", 334 | "(",{pid, [pid], "", {bad_align,bad20}},")"] 335 | ))) 336 | }, 337 | {"Metadata can have extra formatting with width 2", 338 | ?_assertEqual(iolist_to_binary(["(hello )"]), 339 | iolist_to_binary(format(lager_msg:new("Message", 340 | Now, 341 | error, 342 | [{pid, hello}], 343 | []), 344 | ["(",{pid, [pid], "", {left,10}},")"] 345 | ))) 346 | }, 347 | {"Metadata can have extra formatting with width 3", 348 | ?_assertEqual(iolist_to_binary(["( hello)"]), 349 | iolist_to_binary(format(lager_msg:new("Message", 350 | Now, 351 | error, 352 | [{pid, hello}], 353 | []), 354 | ["(",{pid, [pid], "", {right,10}},")"] 355 | ))) 356 | }, 357 | {"Metadata can have extra formatting with width 4", 358 | ?_assertEqual(iolist_to_binary(["( hello )"]), 359 | iolist_to_binary(format(lager_msg:new("Message", 360 | Now, 361 | error, 362 | [{pid, hello}], 363 | []), 364 | ["(",{pid, [pid], "", {centre,10}},")"] 365 | ))) 366 | }, 367 | {"Metadata can have extra formatting with width 5", 368 | ?_assertEqual(iolist_to_binary(["error |hello ! ( hello )"]), 369 | iolist_to_binary(format(lager_msg:new("Message", 370 | Now, 371 | error, 372 | [{pid, hello}], 373 | []), 374 | [{x,"",[severity,{blank,"|"},pid], 10},"!",blank,"(",{pid, [pid], "", {centre,10}},")"] 375 | ))) 376 | }, 377 | {"Metadata can have extra formatting with width 6", 378 | ?_assertEqual(iolist_to_binary([Time,Date," bar=2 baz=3 foo=1 pid=hello EMessage"]), 379 | iolist_to_binary(format(lager_msg:new("Message", 380 | Now, 381 | error, 382 | [{pid, hello},{foo, 1}, {bar, 2}, {baz, 3}], 383 | []), 384 | [{x,"",[time]}, {x,"",[date],20},blank,{x,"",[metadata],30},blank,{x,"",[sev],10},message, {message,message,"", {right,20}}] 385 | ))) 386 | } 387 | 388 | 389 | 390 | 391 | ]. 392 | 393 | -endif. 394 | -------------------------------------------------------------------------------- /src/lager_handler_watcher.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. 2 | %% 3 | %% This file is provided to you under the Apache License, 4 | %% Version 2.0 (the "License"); you may not use this file 5 | %% except in compliance with the License. You may obtain 6 | %% a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, 11 | %% software distributed under the License is distributed on an 12 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | %% KIND, either express or implied. See the License for the 14 | %% specific language governing permissions and limitations 15 | %% under the License. 16 | 17 | %% @doc A process that does a gen_event:add_sup_handler and attempts to re-add 18 | %% event handlers when they exit. 19 | 20 | %% @private 21 | 22 | -module(lager_handler_watcher). 23 | 24 | -behaviour(gen_server). 25 | 26 | -include("lager.hrl"). 27 | 28 | -ifdef(TEST). 29 | -include_lib("eunit/include/eunit.hrl"). 30 | -endif. 31 | 32 | %% callbacks 33 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, 34 | code_change/3]). 35 | 36 | -export([start_link/3, start/3]). 37 | 38 | -record(state, { 39 | module :: atom(), 40 | config :: any(), 41 | sink :: pid() | atom() 42 | }). 43 | 44 | start_link(Sink, Module, Config) -> 45 | gen_server:start_link(?MODULE, [Sink, Module, Config], []). 46 | 47 | start(Sink, Module, Config) -> 48 | gen_server:start(?MODULE, [Sink, Module, Config], []). 49 | 50 | init([Sink, Module, Config]) -> 51 | install_handler(Sink, Module, Config), 52 | {ok, #state{sink=Sink, module=Module, config=Config}}. 53 | 54 | handle_call(_Call, _From, State) -> 55 | {reply, ok, State}. 56 | 57 | handle_cast(_Request, State) -> 58 | {noreply, State}. 59 | 60 | handle_info({gen_event_EXIT, Module, normal}, #state{module=Module} = State) -> 61 | {stop, normal, State}; 62 | handle_info({gen_event_EXIT, Module, shutdown}, #state{module=Module} = State) -> 63 | {stop, normal, State}; 64 | handle_info({gen_event_EXIT, Module, {'EXIT', {kill_me, [_KillerHWM, KillerReinstallAfter]}}}, 65 | #state{module=Module, sink=Sink} = State) -> 66 | %% Brutally kill the manager but stay alive to restore settings. 67 | %% 68 | %% SinkPid here means the gen_event process. Handlers *all* live inside the 69 | %% same gen_event process space, so when the Pid is killed, *all* of the 70 | %% pending log messages in its mailbox will die too. 71 | SinkPid = whereis(Sink), 72 | unlink(SinkPid), 73 | exit(SinkPid, kill), 74 | erlang:send_after(KillerReinstallAfter, self(), {reboot, Sink}), 75 | {noreply, State}; 76 | handle_info({gen_event_EXIT, Module, Reason}, #state{module=Module, 77 | config=Config, sink=Sink} = State) -> 78 | case lager:log(error, self(), "Lager event handler ~p exited with reason ~s", 79 | [Module, error_logger_lager_h:format_reason(Reason)]) of 80 | ok -> 81 | install_handler(Sink, Module, Config); 82 | {error, _} -> 83 | %% lager is not working, so installing a handler won't work 84 | ok 85 | end, 86 | {noreply, State}; 87 | handle_info(reinstall_handler, #state{module=Module, config=Config, sink=Sink} = State) -> 88 | install_handler(Sink, Module, Config), 89 | {noreply, State}; 90 | handle_info({reboot, Sink}, State) -> 91 | _ = lager_app:boot(Sink), 92 | {noreply, State}; 93 | handle_info(stop, State) -> 94 | {stop, normal, State}; 95 | handle_info(_Info, State) -> 96 | {noreply, State}. 97 | 98 | terminate(_Reason, _State) -> 99 | ok. 100 | 101 | code_change(_OldVsn, State, _Extra) -> 102 | {ok, State}. 103 | 104 | %% internal 105 | install_handler(Sink, lager_backend_throttle, Config) -> 106 | %% The lager_backend_throttle needs to know to which sink it is 107 | %% attached, hence this admittedly ugly workaround. Handlers are 108 | %% sensitive to the structure of the configuration sent to `init', 109 | %% sadly, so it's not trivial to add a configuration item to be 110 | %% ignored to backends without breaking 3rd party handlers. 111 | install_handler2(Sink, lager_backend_throttle, [{sink, Sink}|Config]); 112 | install_handler(Sink, Module, Config) -> 113 | install_handler2(Sink, Module, Config). 114 | 115 | %% private 116 | install_handler2(Sink, Module, Config) -> 117 | case gen_event:add_sup_handler(Sink, Module, Config) of 118 | ok -> 119 | ?INT_LOG(debug, "Lager installed handler ~p into ~p", [Module, Sink]), 120 | lager:update_loglevel_config(Sink), 121 | ok; 122 | {error, {fatal, Reason}} -> 123 | ?INT_LOG(error, "Lager fatally failed to install handler ~p into" 124 | " ~p, NOT retrying: ~p", [Module, Sink, Reason]), 125 | %% tell ourselves to stop 126 | self() ! stop, 127 | ok; 128 | Error -> 129 | %% try to reinstall it later 130 | ?INT_LOG(error, "Lager failed to install handler ~p into" 131 | " ~p, retrying later : ~p", [Module, Sink, Error]), 132 | erlang:send_after(5000, self(), reinstall_handler), 133 | ok 134 | end. 135 | 136 | -ifdef(TEST). 137 | 138 | from_now(Seconds) -> 139 | {Mega, Secs, Micro} = os:timestamp(), 140 | {Mega, Secs + Seconds, Micro}. 141 | 142 | reinstall_on_initial_failure_test_() -> 143 | {timeout, 60000, 144 | [ 145 | fun() -> 146 | error_logger:tty(false), 147 | application:load(lager), 148 | application:set_env(lager, handlers, [{lager_test_backend, info}, {lager_crash_backend, [from_now(2), undefined]}]), 149 | application:set_env(lager, error_logger_redirect, false), 150 | application:unset_env(lager, crash_log), 151 | lager:start(), 152 | try 153 | {_Level, _Time, Message, _Metadata} = lager_test_backend:pop(), 154 | ?assertMatch("Lager failed to install handler lager_crash_backend into lager_event, retrying later :"++_, lists:flatten(Message)), 155 | timer:sleep(6000), 156 | lager_test_backend:flush(), 157 | ?assertEqual(0, lager_test_backend:count()), 158 | ?assert(lists:member(lager_crash_backend, gen_event:which_handlers(lager_event))) 159 | after 160 | application:stop(lager), 161 | application:stop(goldrush), 162 | error_logger:tty(true) 163 | end 164 | end 165 | ] 166 | }. 167 | 168 | reinstall_on_runtime_failure_test_() -> 169 | {timeout, 60000, 170 | [ 171 | fun() -> 172 | error_logger:tty(false), 173 | application:load(lager), 174 | application:set_env(lager, handlers, [{lager_test_backend, info}, {lager_crash_backend, [undefined, from_now(5)]}]), 175 | application:set_env(lager, error_logger_redirect, false), 176 | application:unset_env(lager, crash_log), 177 | lager:start(), 178 | try 179 | ?assert(lists:member(lager_crash_backend, gen_event:which_handlers(lager_event))), 180 | timer:sleep(6000), 181 | 182 | pop_until("Lager event handler lager_crash_backend exited with reason crash", fun lists:flatten/1), 183 | pop_until("Lager failed to install handler lager_crash_backend into lager_event, retrying later", 184 | fun(Msg) -> string:substr(lists:flatten(Msg), 1, 84) end), 185 | ?assertEqual(false, lists:member(lager_crash_backend, gen_event:which_handlers(lager_event))) 186 | after 187 | application:stop(lager), 188 | application:stop(goldrush), 189 | error_logger:tty(true) 190 | end 191 | end 192 | ] 193 | }. 194 | 195 | pop_until(String, Fun) -> 196 | try_backend_pop(lager_test_backend:pop(), String, Fun). 197 | 198 | try_backend_pop(undefined, String, _Fun) -> 199 | throw("Not found: " ++ String); 200 | try_backend_pop({_Severity, _Date, Msg, _Metadata}, String, Fun) -> 201 | case Fun(Msg) of 202 | String -> 203 | ok; 204 | _ -> 205 | try_backend_pop(lager_test_backend:pop(), String, Fun) 206 | end. 207 | 208 | -endif. 209 | -------------------------------------------------------------------------------- /src/lager_handler_watcher_sup.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. 2 | %% 3 | %% This file is provided to you under the Apache License, 4 | %% Version 2.0 (the "License"); you may not use this file 5 | %% except in compliance with the License. You may obtain 6 | %% a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, 11 | %% software distributed under the License is distributed on an 12 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | %% KIND, either express or implied. See the License for the 14 | %% specific language governing permissions and limitations 15 | %% under the License. 16 | 17 | %% @doc A supervisor for monitoring lager_handler_watcher processes. 18 | 19 | %% @private 20 | 21 | -module(lager_handler_watcher_sup). 22 | 23 | -behaviour(supervisor). 24 | 25 | %% API 26 | -export([start_link/0]). 27 | 28 | %% Callbacks 29 | -export([init/1]). 30 | 31 | start_link() -> 32 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 33 | 34 | init([]) -> 35 | {ok, {{simple_one_for_one, 10, 60}, 36 | [ 37 | {lager_handler_watcher, {lager_handler_watcher, start_link, []}, 38 | temporary, 5000, worker, [lager_handler_watcher]} 39 | ]}}. 40 | -------------------------------------------------------------------------------- /src/lager_manager_killer.erl: -------------------------------------------------------------------------------- 1 | -module(lager_manager_killer). 2 | -author("Sungjin Park "). 3 | -behavior(gen_event). 4 | 5 | -export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2, code_change/3]). 6 | 7 | -export([kill_me/0]). 8 | 9 | -include("lager.hrl"). 10 | 11 | -record(state, { 12 | killer_hwm :: non_neg_integer(), 13 | killer_reinstall_after :: non_neg_integer() 14 | }). 15 | 16 | kill_me() -> 17 | gen_event:call(lager_event, ?MODULE, kill_self). 18 | 19 | init([KillerHWM, KillerReinstallAfter]) -> 20 | {ok, #state{killer_hwm=KillerHWM, killer_reinstall_after=KillerReinstallAfter}}. 21 | 22 | handle_call(get_loglevel, State) -> 23 | {ok, {mask, ?LOG_NONE}, State}; 24 | handle_call({set_loglevel, _Level}, State) -> 25 | {ok, ok, State}; 26 | handle_call(get_settings, State = #state{killer_hwm=KillerHWM, killer_reinstall_after=KillerReinstallAfter}) -> 27 | {ok, [KillerHWM, KillerReinstallAfter], State}; 28 | handle_call(kill_self, #state{killer_hwm=KillerHWM, killer_reinstall_after=KillerReinstallAfter}) -> 29 | exit({kill_me, [KillerHWM, KillerReinstallAfter]}); 30 | handle_call(_Request, State) -> 31 | {ok, ok, State}. 32 | %% It's not the best idea in the world to check the queue length for every 33 | %% log message. We can make this operation work on a poll timer in the 34 | %% future. 35 | handle_event({log, _Message}, State = #state{killer_hwm=KillerHWM, killer_reinstall_after=KillerReinstallAfter}) -> 36 | {message_queue_len, Len} = process_info(self(), message_queue_len), 37 | case Len > KillerHWM of 38 | true -> 39 | exit({kill_me, [KillerHWM, KillerReinstallAfter]}); 40 | _ -> 41 | {ok, State} 42 | end; 43 | handle_event(_Event, State) -> 44 | {ok, State}. 45 | 46 | handle_info(_Info, State) -> 47 | {ok, State}. 48 | 49 | terminate(_Reason, _State) -> 50 | ok. 51 | 52 | code_change(_OldVsn, State, _Extra) -> 53 | {ok, State}. 54 | -------------------------------------------------------------------------------- /src/lager_msg.erl: -------------------------------------------------------------------------------- 1 | -module(lager_msg). 2 | 3 | -export([new/4, new/5]). 4 | -export([message/1]). 5 | -export([timestamp/1]). 6 | -export([datetime/1]). 7 | -export([severity/1]). 8 | -export([severity_as_int/1]). 9 | -export([metadata/1]). 10 | -export([destinations/1]). 11 | 12 | -record(lager_msg,{ 13 | destinations :: list(), 14 | metadata :: [tuple()], 15 | severity :: lager:log_level(), 16 | datetime :: {string(), string()}, 17 | timestamp :: erlang:timestamp(), 18 | message :: list() 19 | }). 20 | 21 | -opaque lager_msg() :: #lager_msg{}. 22 | -export_type([lager_msg/0]). 23 | 24 | %% create with provided timestamp, handy for testing mostly 25 | -spec new(list(), erlang:timestamp(), lager:log_level(), [tuple()], list()) -> lager_msg(). 26 | new(Msg, Timestamp, Severity, Metadata, Destinations) -> 27 | {Date, Time} = lager_util:format_time(lager_util:maybe_utc(lager_util:localtime_ms(Timestamp))), 28 | #lager_msg{message=Msg, datetime={Date, Time}, timestamp=Timestamp, severity=Severity, 29 | metadata=Metadata, destinations=Destinations}. 30 | 31 | -spec new(list(), lager:log_level(), [tuple()], list()) -> lager_msg(). 32 | new(Msg, Severity, Metadata, Destinations) -> 33 | Now = os:timestamp(), 34 | new(Msg, Now, Severity, Metadata, Destinations). 35 | 36 | -spec message(lager_msg()) -> list(). 37 | message(Msg) -> 38 | Msg#lager_msg.message. 39 | 40 | -spec timestamp(lager_msg()) -> erlang:timestamp(). 41 | timestamp(Msg) -> 42 | Msg#lager_msg.timestamp. 43 | 44 | -spec datetime(lager_msg()) -> {string(), string()}. 45 | datetime(Msg) -> 46 | Msg#lager_msg.datetime. 47 | 48 | -spec severity(lager_msg()) -> lager:log_level(). 49 | severity(Msg) -> 50 | Msg#lager_msg.severity. 51 | 52 | -spec severity_as_int(lager_msg()) -> lager:log_level_number(). 53 | severity_as_int(Msg) -> 54 | lager_util:level_to_num(Msg#lager_msg.severity). 55 | 56 | -spec metadata(lager_msg()) -> [tuple()]. 57 | metadata(Msg) -> 58 | Msg#lager_msg.metadata. 59 | 60 | -spec destinations(lager_msg()) -> list(). 61 | destinations(Msg) -> 62 | Msg#lager_msg.destinations. 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/lager_stdlib.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 1996-2009. All Rights Reserved. 5 | %% 6 | %% The contents of this file are subject to the Erlang Public License, 7 | %% Version 1.1, (the "License"); you may not use this file except in 8 | %% compliance with the License. You should have received a copy of the 9 | %% Erlang Public License along with this software. If not, it can be 10 | %% retrieved online at http://www.erlang.org/. 11 | %% 12 | %% Software distributed under the License is distributed on an "AS IS" 13 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 14 | %% the License for the specific language governing rights and limitations 15 | %% under the License. 16 | %% 17 | %% %CopyrightEnd% 18 | %% 19 | 20 | %% @doc Functions from Erlang OTP distribution that are really useful 21 | %% but aren't exported. 22 | %% 23 | %% All functions in this module are covered by the Erlang/OTP source 24 | %% distribution's license, the Erlang Public License. See 25 | %% http://www.erlang.org/ for full details. 26 | 27 | -module(lager_stdlib). 28 | 29 | -export([string_p/1]). 30 | -export([write_time/2, maybe_utc/1]). 31 | -export([is_my_error_report/1, is_my_info_report/1]). 32 | -export([sup_get/2]). 33 | -export([proc_lib_format/2]). 34 | 35 | 36 | %% from error_logger_file_h 37 | string_p([]) -> 38 | false; 39 | string_p(Term) -> 40 | string_p1(Term). 41 | 42 | string_p1([H|T]) when is_integer(H), H >= $\s, H < 256 -> 43 | string_p1(T); 44 | string_p1([$\n|T]) -> string_p1(T); 45 | string_p1([$\r|T]) -> string_p1(T); 46 | string_p1([$\t|T]) -> string_p1(T); 47 | string_p1([$\v|T]) -> string_p1(T); 48 | string_p1([$\b|T]) -> string_p1(T); 49 | string_p1([$\f|T]) -> string_p1(T); 50 | string_p1([$\e|T]) -> string_p1(T); 51 | string_p1([H|T]) when is_list(H) -> 52 | case string_p1(H) of 53 | true -> string_p1(T); 54 | _ -> false 55 | end; 56 | string_p1([]) -> true; 57 | string_p1(_) -> false. 58 | 59 | %% From calendar 60 | -type year1970() :: 1970..10000. % should probably be 1970.. 61 | -type month() :: 1..12. 62 | -type day() :: 1..31. 63 | -type hour() :: 0..23. 64 | -type minute() :: 0..59. 65 | -type second() :: 0..59. 66 | -type t_time() :: {hour(),minute(),second()}. 67 | -type t_datetime1970() :: {{year1970(),month(),day()},t_time()}. 68 | 69 | %% From OTP stdlib's error_logger_tty_h.erl ... These functions aren't 70 | %% exported. 71 | -spec write_time({utc, t_datetime1970()} | t_datetime1970(), string()) -> string(). 72 | write_time({utc,{{Y,Mo,D},{H,Mi,S}}},Type) -> 73 | io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s UTC ===~n", 74 | [Type,D,month(Mo),Y,t(H),t(Mi),t(S)]); 75 | write_time({{Y,Mo,D},{H,Mi,S}},Type) -> 76 | io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s ===~n", 77 | [Type,D,month(Mo),Y,t(H),t(Mi),t(S)]). 78 | 79 | -spec maybe_utc(t_datetime1970()) -> {utc, t_datetime1970()} | t_datetime1970(). 80 | maybe_utc(Time) -> 81 | UTC = case application:get_env(sasl, utc_log) of 82 | {ok, Val} -> 83 | Val; 84 | undefined -> 85 | %% Backwards compatible: 86 | lager_app:get_env(stdlib, utc_log, false) 87 | end, 88 | if 89 | UTC =:= true -> 90 | UTCTime = case calendar:local_time_to_universal_time_dst(Time) of 91 | [] -> calendar:local_time(); 92 | [T0|_] -> T0 93 | end, 94 | {utc, UTCTime}; 95 | true -> 96 | Time 97 | end. 98 | 99 | t(X) when is_integer(X) -> 100 | t1(integer_to_list(X)); 101 | t(_) -> 102 | "". 103 | t1([X]) -> [$0,X]; 104 | t1(X) -> X. 105 | 106 | month(1) -> "Jan"; 107 | month(2) -> "Feb"; 108 | month(3) -> "Mar"; 109 | month(4) -> "Apr"; 110 | month(5) -> "May"; 111 | month(6) -> "Jun"; 112 | month(7) -> "Jul"; 113 | month(8) -> "Aug"; 114 | month(9) -> "Sep"; 115 | month(10) -> "Oct"; 116 | month(11) -> "Nov"; 117 | month(12) -> "Dec". 118 | 119 | %% From OTP sasl's sasl_report.erl ... These functions aren't 120 | %% exported. 121 | -spec is_my_error_report(atom()) -> boolean(). 122 | is_my_error_report(supervisor_report) -> true; 123 | is_my_error_report(crash_report) -> true; 124 | is_my_error_report(_) -> false. 125 | 126 | -spec is_my_info_report(atom()) -> boolean(). 127 | is_my_info_report(progress) -> true; 128 | is_my_info_report(_) -> false. 129 | 130 | -spec sup_get(term(), [proplists:property()]) -> term(). 131 | sup_get(Tag, Report) -> 132 | case lists:keysearch(Tag, 1, Report) of 133 | {value, {_, Value}} -> 134 | Value; 135 | _ -> 136 | "" 137 | end. 138 | 139 | %% From OTP stdlib's proc_lib.erl ... These functions aren't exported. 140 | -spec proc_lib_format([term()], pos_integer()) -> string(). 141 | proc_lib_format([OwnReport,LinkReport], FmtMaxBytes) -> 142 | OwnFormat = format_report(OwnReport, FmtMaxBytes), 143 | LinkFormat = format_report(LinkReport, FmtMaxBytes), 144 | %% io_lib:format here is OK because we're limiting max length elsewhere. 145 | Str = io_lib:format(" crasher:~n~s neighbours:~n~s",[OwnFormat,LinkFormat]), 146 | lists:flatten(Str). 147 | 148 | format_report(Rep, FmtMaxBytes) when is_list(Rep) -> 149 | format_rep(Rep, FmtMaxBytes); 150 | format_report(Rep, FmtMaxBytes) -> 151 | {Str, _} = lager_trunc_io:print(Rep, FmtMaxBytes), 152 | io_lib:format("~p~n", [Str]). 153 | 154 | format_rep([{initial_call,InitialCall}|Rep], FmtMaxBytes) -> 155 | [format_mfa(InitialCall, FmtMaxBytes)|format_rep(Rep, FmtMaxBytes)]; 156 | format_rep([{error_info,{Class,Reason,StackTrace}}|Rep], FmtMaxBytes) -> 157 | [format_exception(Class, Reason, StackTrace, FmtMaxBytes)|format_rep(Rep, FmtMaxBytes)]; 158 | format_rep([{Tag,Data}|Rep], FmtMaxBytes) -> 159 | [format_tag(Tag, Data, FmtMaxBytes)|format_rep(Rep, FmtMaxBytes)]; 160 | format_rep(_, _S) -> 161 | []. 162 | 163 | format_exception(Class, Reason, StackTrace, FmtMaxBytes) -> 164 | PF = pp_fun(FmtMaxBytes), 165 | StackFun = fun(M, _F, _A) -> (M =:= erl_eval) or (M =:= ?MODULE) end, 166 | %% EI = " exception: ", 167 | EI = " ", 168 | [EI, lib_format_exception(1+length(EI), Class, Reason, 169 | StackTrace, StackFun, PF), "\n"]. 170 | 171 | format_mfa({M,F,Args}=StartF, FmtMaxBytes) -> 172 | try 173 | A = length(Args), 174 | [" initial call: ",atom_to_list(M),$:,atom_to_list(F),$/, 175 | integer_to_list(A),"\n"] 176 | catch 177 | error:_ -> 178 | format_tag(initial_call, StartF, FmtMaxBytes) 179 | end. 180 | 181 | pp_fun(FmtMaxBytes) -> 182 | fun(Term, _I) -> 183 | {Str, _} = lager_trunc_io:print(Term, FmtMaxBytes), 184 | io_lib:format("~s", [Str]) 185 | end. 186 | 187 | format_tag(Tag, Data, FmtMaxBytes) -> 188 | {Str, _} = lager_trunc_io:print(Data, FmtMaxBytes), 189 | io_lib:format(" ~p: ~s~n", [Tag, Str]). 190 | 191 | %% From OTP stdlib's lib.erl ... These functions aren't exported. 192 | 193 | lib_format_exception(I, Class, Reason, StackTrace, StackFun, FormatFun) 194 | when is_integer(I), I >= 1, is_function(StackFun, 3), 195 | is_function(FormatFun, 2) -> 196 | Str = n_spaces(I-1), 197 | {Term,Trace1,Trace} = analyze_exception(Class, Reason, StackTrace), 198 | Expl0 = explain_reason(Term, Class, Trace1, FormatFun, Str), 199 | Expl = io_lib:fwrite(<<"~s~s">>, [exited(Class), Expl0]), 200 | case format_stacktrace1(Str, Trace, FormatFun, StackFun) of 201 | [] -> Expl; 202 | Stack -> [Expl, $\n, Stack] 203 | end. 204 | 205 | analyze_exception(error, Term, Stack) -> 206 | case {is_stacktrace(Stack), Stack, Term} of 207 | {true, [{_M,_F,As}=MFA|MFAs], function_clause} when is_list(As) -> 208 | {Term,[MFA],MFAs}; 209 | {true, [{shell,F,A}], function_clause} when is_integer(A) -> 210 | {Term, [{F,A}], []}; 211 | {true, [{_M,_F,_AorAs}=MFA|MFAs], undef} -> 212 | {Term,[MFA],MFAs}; 213 | {true, _, _} -> 214 | {Term,[],Stack}; 215 | {false, _, _} -> 216 | {{Term,Stack},[],[]} 217 | end; 218 | analyze_exception(_Class, Term, Stack) -> 219 | case is_stacktrace(Stack) of 220 | true -> 221 | {Term,[],Stack}; 222 | false -> 223 | {{Term,Stack},[],[]} 224 | end. 225 | 226 | is_stacktrace([]) -> 227 | true; 228 | is_stacktrace([{M,F,A}|Fs]) when is_atom(M), is_atom(F), is_integer(A) -> 229 | is_stacktrace(Fs); 230 | is_stacktrace([{M,F,As}|Fs]) when is_atom(M), is_atom(F), length(As) >= 0 -> 231 | is_stacktrace(Fs); 232 | is_stacktrace(_) -> 233 | false. 234 | 235 | %% ERTS exit codes (some of them are also returned by erl_eval): 236 | explain_reason(badarg, error, [], _PF, _Str) -> 237 | <<"bad argument">>; 238 | explain_reason({badarg,V}, error=Cl, [], PF, Str) -> % orelse, andalso 239 | format_value(V, <<"bad argument: ">>, Cl, PF, Str); 240 | explain_reason(badarith, error, [], _PF, _Str) -> 241 | <<"bad argument in an arithmetic expression">>; 242 | explain_reason({badarity,{Fun,As}}, error, [], _PF, _Str) 243 | when is_function(Fun) -> 244 | %% Only the arity is displayed, not the arguments As. 245 | io_lib:fwrite(<<"~s called with ~s">>, 246 | [format_fun(Fun), argss(length(As))]); 247 | explain_reason({badfun,Term}, error=Cl, [], PF, Str) -> 248 | format_value(Term, <<"bad function ">>, Cl, PF, Str); 249 | explain_reason({badmatch,Term}, error=Cl, [], PF, Str) -> 250 | format_value(Term, <<"no match of right hand side value ">>, Cl, PF, Str); 251 | explain_reason({case_clause,V}, error=Cl, [], PF, Str) -> 252 | %% "there is no case clause with a true guard sequence and a 253 | %% pattern matching..." 254 | format_value(V, <<"no case clause matching ">>, Cl, PF, Str); 255 | explain_reason(function_clause, error, [{F,A}], _PF, _Str) -> 256 | %% Shell commands 257 | FAs = io_lib:fwrite(<<"~w/~w">>, [F, A]), 258 | [<<"no function clause matching call to ">> | FAs]; 259 | explain_reason(function_clause, error=Cl, [{M,F,As}], PF, Str) -> 260 | String = <<"no function clause matching ">>, 261 | format_errstr_call(String, Cl, {M,F}, As, PF, Str); 262 | explain_reason(if_clause, error, [], _PF, _Str) -> 263 | <<"no true branch found when evaluating an if expression">>; 264 | explain_reason(noproc, error, [], _PF, _Str) -> 265 | <<"no such process or port">>; 266 | explain_reason(notalive, error, [], _PF, _Str) -> 267 | <<"the node cannot be part of a distributed system">>; 268 | explain_reason(system_limit, error, [], _PF, _Str) -> 269 | <<"a system limit has been reached">>; 270 | explain_reason(timeout_value, error, [], _PF, _Str) -> 271 | <<"bad receive timeout value">>; 272 | explain_reason({try_clause,V}, error=Cl, [], PF, Str) -> 273 | %% "there is no try clause with a true guard sequence and a 274 | %% pattern matching..." 275 | format_value(V, <<"no try clause matching ">>, Cl, PF, Str); 276 | explain_reason(undef, error, [{M,F,A}], _PF, _Str) -> 277 | %% Only the arity is displayed, not the arguments, if there are any. 278 | io_lib:fwrite(<<"undefined function ~s">>, 279 | [mfa_to_string(M, F, n_args(A))]); 280 | explain_reason({shell_undef,F,A}, error, [], _PF, _Str) -> 281 | %% Give nicer reports for undefined shell functions 282 | %% (but not when the user actively calls shell_default:F(...)). 283 | io_lib:fwrite(<<"undefined shell command ~s/~w">>, [F, n_args(A)]); 284 | %% Exit codes returned by erl_eval only: 285 | explain_reason({argument_limit,_Fun}, error, [], _PF, _Str) -> 286 | io_lib:fwrite(<<"limit of number of arguments to interpreted function" 287 | " exceeded">>, []); 288 | explain_reason({bad_filter,V}, error=Cl, [], PF, Str) -> 289 | format_value(V, <<"bad filter ">>, Cl, PF, Str); 290 | explain_reason({bad_generator,V}, error=Cl, [], PF, Str) -> 291 | format_value(V, <<"bad generator ">>, Cl, PF, Str); 292 | explain_reason({unbound,V}, error, [], _PF, _Str) -> 293 | io_lib:fwrite(<<"variable ~w is unbound">>, [V]); 294 | %% Exit codes local to the shell module (restricted shell): 295 | explain_reason({restricted_shell_bad_return, V}, exit=Cl, [], PF, Str) -> 296 | String = <<"restricted shell module returned bad value ">>, 297 | format_value(V, String, Cl, PF, Str); 298 | explain_reason({restricted_shell_disallowed,{ForMF,As}}, 299 | exit=Cl, [], PF, Str) -> 300 | %% ForMF can be a fun, but not a shell fun. 301 | String = <<"restricted shell does not allow ">>, 302 | format_errstr_call(String, Cl, ForMF, As, PF, Str); 303 | explain_reason(restricted_shell_started, exit, [], _PF, _Str) -> 304 | <<"restricted shell starts now">>; 305 | explain_reason(restricted_shell_stopped, exit, [], _PF, _Str) -> 306 | <<"restricted shell stopped">>; 307 | %% Other exit code: 308 | explain_reason(Reason, Class, [], PF, Str) -> 309 | PF(Reason, (iolist_size(Str)+1) + exited_size(Class)). 310 | 311 | n_spaces(N) -> 312 | lists:duplicate(N, $\s). 313 | 314 | exited_size(Class) -> 315 | iolist_size(exited(Class)). 316 | 317 | exited(error) -> 318 | <<"exception error: ">>; 319 | exited(exit) -> 320 | <<"exception exit: ">>; 321 | exited(throw) -> 322 | <<"exception throw: ">>. 323 | 324 | format_stacktrace1(S0, Stack0, PF, SF) -> 325 | Stack1 = lists:dropwhile(fun({M,F,A}) -> SF(M, F, A) 326 | end, lists:reverse(Stack0)), 327 | S = [" " | S0], 328 | Stack = lists:reverse(Stack1), 329 | format_stacktrace2(S, Stack, 1, PF). 330 | 331 | format_stacktrace2(S, [{M,F,A}|Fs], N, PF) when is_integer(A) -> 332 | [io_lib:fwrite(<<"~s~s ~s">>, 333 | [sep(N, S), origin(N, M, F, A), mfa_to_string(M, F, A)]) 334 | | format_stacktrace2(S, Fs, N + 1, PF)]; 335 | format_stacktrace2(S, [{M,F,As}|Fs], N, PF) when is_list(As) -> 336 | A = length(As), 337 | CalledAs = [S,<<" called as ">>], 338 | C = format_call("", CalledAs, {M,F}, As, PF), 339 | [io_lib:fwrite(<<"~s~s ~s\n~s~s">>, 340 | [sep(N, S), origin(N, M, F, A), mfa_to_string(M, F, A), 341 | CalledAs, C]) 342 | | format_stacktrace2(S, Fs, N + 1, PF)]; 343 | format_stacktrace2(_S, [], _N, _PF) -> 344 | "". 345 | 346 | argss(0) -> 347 | <<"no arguments">>; 348 | argss(1) -> 349 | <<"one argument">>; 350 | argss(2) -> 351 | <<"two arguments">>; 352 | argss(I) -> 353 | io_lib:fwrite(<<"~w arguments">>, [I]). 354 | 355 | format_value(V, ErrStr, Class, PF, Str) -> 356 | Pre1Sz = exited_size(Class), 357 | Str1 = PF(V, Pre1Sz + iolist_size([Str, ErrStr])+1), 358 | [ErrStr | case count_nl(Str1) of 359 | N1 when N1 > 1 -> 360 | Str2 = PF(V, iolist_size(Str) + 1 + Pre1Sz), 361 | case count_nl(Str2) < N1 of 362 | true -> 363 | [$\n, Str, n_spaces(Pre1Sz) | Str2]; 364 | false -> 365 | Str1 366 | end; 367 | _ -> 368 | Str1 369 | end]. 370 | 371 | format_fun(Fun) when is_function(Fun) -> 372 | {module, M} = erlang:fun_info(Fun, module), 373 | {name, F} = erlang:fun_info(Fun, name), 374 | {arity, A} = erlang:fun_info(Fun, arity), 375 | case erlang:fun_info(Fun, type) of 376 | {type, local} when F =:= "" -> 377 | io_lib:fwrite(<<"~w">>, [Fun]); 378 | {type, local} when M =:= erl_eval -> 379 | io_lib:fwrite(<<"interpreted function with arity ~w">>, [A]); 380 | {type, local} -> 381 | mfa_to_string(M, F, A); 382 | {type, external} -> 383 | mfa_to_string(M, F, A) 384 | end. 385 | 386 | format_errstr_call(ErrStr, Class, ForMForFun, As, PF, Pre0) -> 387 | Pre1 = [Pre0 | n_spaces(exited_size(Class))], 388 | format_call(ErrStr, Pre1, ForMForFun, As, PF). 389 | 390 | format_call(ErrStr, Pre1, ForMForFun, As, PF) -> 391 | Arity = length(As), 392 | [ErrStr | 393 | case is_op(ForMForFun, Arity) of 394 | {yes,Op} -> 395 | format_op(ErrStr, Pre1, Op, As, PF); 396 | no -> 397 | MFs = mf_to_string(ForMForFun, Arity), 398 | I1 = iolist_size([Pre1,ErrStr|MFs]), 399 | S1 = pp_arguments(PF, As, I1), 400 | S2 = pp_arguments(PF, As, iolist_size([Pre1|MFs])), 401 | Long = count_nl(pp_arguments(PF, [a2345,b2345], I1)) > 0, 402 | case Long or (count_nl(S2) < count_nl(S1)) of 403 | true -> 404 | [$\n, Pre1, MFs, S2]; 405 | false -> 406 | [MFs, S1] 407 | end 408 | end]. 409 | 410 | mfa_to_string(M, F, A) -> 411 | io_lib:fwrite(<<"~s/~w">>, [mf_to_string({M, F}, A), A]). 412 | 413 | mf_to_string({M, F}, A) -> 414 | case erl_internal:bif(M, F, A) of 415 | true -> 416 | io_lib:fwrite(<<"~w">>, [F]); 417 | false -> 418 | case is_op({M, F}, A) of 419 | {yes, '/'} -> 420 | io_lib:fwrite(<<"~w">>, [F]); 421 | {yes, F} -> 422 | atom_to_list(F); 423 | no -> 424 | io_lib:fwrite(<<"~w:~w">>, [M, F]) 425 | end 426 | end; 427 | mf_to_string(Fun, _A) when is_function(Fun) -> 428 | format_fun(Fun); 429 | mf_to_string(F, _A) -> 430 | io_lib:fwrite(<<"~w">>, [F]). 431 | 432 | n_args(A) when is_integer(A) -> 433 | A; 434 | n_args(As) when is_list(As) -> 435 | length(As). 436 | 437 | origin(1, M, F, A) -> 438 | case is_op({M, F}, n_args(A)) of 439 | {yes, F} -> <<"in operator ">>; 440 | no -> <<"in function ">> 441 | end; 442 | origin(_N, _M, _F, _A) -> 443 | <<"in call from">>. 444 | 445 | sep(1, S) -> S; 446 | sep(_, S) -> [$\n | S]. 447 | 448 | count_nl([E | Es]) -> 449 | count_nl(E) + count_nl(Es); 450 | count_nl($\n) -> 451 | 1; 452 | count_nl(Bin) when is_binary(Bin) -> 453 | count_nl(binary_to_list(Bin)); 454 | count_nl(_) -> 455 | 0. 456 | 457 | is_op(ForMForFun, A) -> 458 | try 459 | {erlang,F} = ForMForFun, 460 | _ = erl_internal:op_type(F, A), 461 | {yes,F} 462 | catch error:_ -> no 463 | end. 464 | 465 | format_op(ErrStr, Pre, Op, [A1, A2], PF) -> 466 | I1 = iolist_size([ErrStr,Pre]), 467 | S1 = PF(A1, I1+1), 468 | S2 = PF(A2, I1+1), 469 | OpS = atom_to_list(Op), 470 | Pre1 = [$\n | n_spaces(I1)], 471 | case count_nl(S1) > 0 of 472 | true -> 473 | [S1,Pre1,OpS,Pre1|S2]; 474 | false -> 475 | OpS2 = io_lib:fwrite(<<" ~s ">>, [Op]), 476 | S2_2 = PF(A2, iolist_size([ErrStr,Pre,S1|OpS2])+1), 477 | case count_nl(S2) < count_nl(S2_2) of 478 | true -> 479 | [S1,Pre1,OpS,Pre1|S2]; 480 | false -> 481 | [S1,OpS2|S2_2] 482 | end 483 | end. 484 | 485 | pp_arguments(PF, As, I) -> 486 | case {As, io_lib:printable_list(As)} of 487 | {[Int | T], true} -> 488 | L = integer_to_list(Int), 489 | Ll = length(L), 490 | A = list_to_atom(lists:duplicate(Ll, $a)), 491 | S0 = binary_to_list(iolist_to_binary(PF([A | T], I+1))), 492 | brackets_to_parens([$[,L,string:sub_string(S0, 2+Ll)]); 493 | _ -> 494 | brackets_to_parens(PF(As, I+1)) 495 | end. 496 | 497 | brackets_to_parens(S) -> 498 | B = iolist_to_binary(S), 499 | Sz = byte_size(B) - 2, 500 | <<$[,R:Sz/binary,$]>> = B, 501 | [$(,R,$)]. 502 | 503 | -------------------------------------------------------------------------------- /src/lager_sup.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. 2 | %% 3 | %% This file is provided to you under the Apache License, 4 | %% Version 2.0 (the "License"); you may not use this file 5 | %% except in compliance with the License. You may obtain 6 | %% a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, 11 | %% software distributed under the License is distributed on an 12 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | %% KIND, either express or implied. See the License for the 14 | %% specific language governing permissions and limitations 15 | %% under the License. 16 | 17 | %% @doc Lager's top level supervisor. 18 | 19 | %% @private 20 | 21 | -module(lager_sup). 22 | 23 | -behaviour(supervisor). 24 | 25 | %% API 26 | -export([start_link/0]). 27 | 28 | %% Callbacks 29 | -export([init/1]). 30 | 31 | start_link() -> 32 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 33 | 34 | init([]) -> 35 | %% set up the config, is safe even during relups 36 | lager_config:new(), 37 | %% TODO: 38 | %% Always start lager_event as the default and make sure that 39 | %% other gen_event stuff can start up as needed 40 | %% 41 | %% Maybe a new API to handle the sink and its policy? 42 | Children = [ 43 | {lager, {gen_event, start_link, [{local, lager_event}]}, 44 | permanent, 5000, worker, dynamic}, 45 | {lager_handler_watcher_sup, {lager_handler_watcher_sup, start_link, []}, 46 | permanent, 5000, supervisor, [lager_handler_watcher_sup]}], 47 | 48 | CrashLog = decide_crash_log(lager_app:get_env(lager, crash_log, false)), 49 | 50 | {ok, {{one_for_one, 10, 60}, 51 | Children ++ CrashLog 52 | }}. 53 | 54 | validate_positive({ok, Val}, _Default) when is_integer(Val) andalso Val >= 0 -> 55 | Val; 56 | validate_positive(_Val, Default) -> 57 | Default. 58 | 59 | determine_rotation_date({ok, ""}) -> 60 | undefined; 61 | determine_rotation_date({ok, Val3}) -> 62 | case lager_util:parse_rotation_date_spec(Val3) of 63 | {ok, Spec} -> Spec; 64 | {error, _} -> 65 | error_logger:error_msg("Invalid date spec for " 66 | "crash log ~p~n", [Val3]), 67 | undefined 68 | end; 69 | determine_rotation_date(_) -> 70 | undefined. 71 | 72 | decide_crash_log(undefined) -> 73 | []; 74 | decide_crash_log(false) -> 75 | []; 76 | decide_crash_log(File) -> 77 | MaxBytes = validate_positive(application:get_env(lager, crash_log_msg_size), 65536), 78 | RotationSize = validate_positive(application:get_env(lager, crash_log_size), 0), 79 | RotationCount = validate_positive(application:get_env(lager, crash_log_count), 0), 80 | 81 | RotationDate = determine_rotation_date(application:get_env(lager, crash_log_date)), 82 | 83 | 84 | [{lager_crash_log, {lager_crash_log, start_link, [File, MaxBytes, 85 | RotationSize, RotationDate, RotationCount]}, 86 | permanent, 5000, worker, [lager_crash_log]}]. 87 | -------------------------------------------------------------------------------- /src/lager_transform.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. 2 | %% 3 | %% This file is provided to you under the Apache License, 4 | %% Version 2.0 (the "License"); you may not use this file 5 | %% except in compliance with the License. You may obtain 6 | %% a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, 11 | %% software distributed under the License is distributed on an 12 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | %% KIND, either express or implied. See the License for the 14 | %% specific language governing permissions and limitations 15 | %% under the License. 16 | 17 | %% @doc The parse transform used for lager messages. 18 | %% This parse transform rewrites functions calls to lager:Severity/1,2 into 19 | %% a more complicated function that captures module, function, line, pid and 20 | %% time as well. The entire function call is then wrapped in a case that 21 | %% checks the lager_config 'loglevel' value, so the code isn't executed if 22 | %% nothing wishes to consume the message. 23 | 24 | -module(lager_transform). 25 | 26 | -include("lager.hrl"). 27 | 28 | -export([parse_transform/2]). 29 | 30 | %% @private 31 | parse_transform(AST, Options) -> 32 | TruncSize = proplists:get_value(lager_truncation_size, Options, ?DEFAULT_TRUNCATION), 33 | Enable = proplists:get_value(lager_print_records_flag, Options, true), 34 | Sinks = [lager] ++ proplists:get_value(lager_extra_sinks, Options, []), 35 | put(print_records_flag, Enable), 36 | put(truncation_size, TruncSize), 37 | put(sinks, Sinks), 38 | erlang:put(records, []), 39 | %% .app file should either be in the outdir, or the same dir as the source file 40 | guess_application(proplists:get_value(outdir, Options), hd(AST)), 41 | walk_ast([], AST). 42 | 43 | walk_ast(Acc, []) -> 44 | case get(print_records_flag) of 45 | true -> 46 | insert_record_attribute(Acc); 47 | false -> 48 | lists:reverse(Acc) 49 | end; 50 | walk_ast(Acc, [{attribute, _, module, {Module, _PmodArgs}}=H|T]) -> 51 | %% A wild parameterized module appears! 52 | put(module, Module), 53 | walk_ast([H|Acc], T); 54 | walk_ast(Acc, [{attribute, _, module, Module}=H|T]) -> 55 | put(module, Module), 56 | walk_ast([H|Acc], T); 57 | walk_ast(Acc, [{function, Line, Name, Arity, Clauses}|T]) -> 58 | put(function, Name), 59 | walk_ast([{function, Line, Name, Arity, 60 | walk_clauses([], Clauses)}|Acc], T); 61 | walk_ast(Acc, [{attribute, _, record, {Name, Fields}}=H|T]) -> 62 | FieldNames = lists:map(fun record_field_name/1, Fields), 63 | stash_record({Name, FieldNames}), 64 | walk_ast([H|Acc], T); 65 | walk_ast(Acc, [H|T]) -> 66 | walk_ast([H|Acc], T). 67 | 68 | record_field_name({record_field, _, {atom, _, FieldName}}) -> 69 | FieldName; 70 | record_field_name({record_field, _, {atom, _, FieldName}, _Default}) -> 71 | FieldName; 72 | record_field_name({typed_record_field, Field, _Type}) -> 73 | record_field_name(Field). 74 | 75 | walk_clauses(Acc, []) -> 76 | lists:reverse(Acc); 77 | walk_clauses(Acc, [{clause, Line, Arguments, Guards, Body}|T]) -> 78 | walk_clauses([{clause, Line, Arguments, Guards, walk_body([], Body)}|Acc], T). 79 | 80 | walk_body(Acc, []) -> 81 | lists:reverse(Acc); 82 | walk_body(Acc, [H|T]) -> 83 | walk_body([transform_statement(H, get(sinks))|Acc], T). 84 | 85 | transform_statement({call, Line, {remote, _Line1, {atom, _Line2, Module}, 86 | {atom, _Line3, Function}}, Arguments0} = Stmt, 87 | Sinks) -> 88 | case lists:member(Module, Sinks) of 89 | true -> 90 | case lists:member(Function, ?LEVELS) of 91 | true -> 92 | SinkName = lager_util:make_internal_sink_name(Module), 93 | do_transform(Line, SinkName, Function, Arguments0); 94 | false -> 95 | case lists:keyfind(Function, 1, ?LEVELS_UNSAFE) of 96 | {Function, Severity} -> 97 | SinkName = lager_util:make_internal_sink_name(Module), 98 | do_transform(Line, SinkName, Severity, Arguments0, unsafe); 99 | false -> 100 | Stmt 101 | end 102 | end; 103 | false -> 104 | list_to_tuple(transform_statement(tuple_to_list(Stmt), Sinks)) 105 | end; 106 | transform_statement(Stmt, Sinks) when is_tuple(Stmt) -> 107 | list_to_tuple(transform_statement(tuple_to_list(Stmt), Sinks)); 108 | transform_statement(Stmt, Sinks) when is_list(Stmt) -> 109 | [transform_statement(S, Sinks) || S <- Stmt]; 110 | transform_statement(Stmt, _Sinks) -> 111 | Stmt. 112 | 113 | do_transform(Line, SinkName, Severity, Arguments0) -> 114 | do_transform(Line, SinkName, Severity, Arguments0, safe). 115 | 116 | do_transform(Line, SinkName, Severity, Arguments0, Safety) -> 117 | SeverityAsInt=lager_util:level_to_num(Severity), 118 | DefaultAttrs0 = {cons, Line, {tuple, Line, [ 119 | {atom, Line, module}, {atom, Line, get(module)}]}, 120 | {cons, Line, {tuple, Line, [ 121 | {atom, Line, function}, {atom, Line, get(function)}]}, 122 | {cons, Line, {tuple, Line, [ 123 | {atom, Line, line}, 124 | {integer, Line, Line}]}, 125 | {cons, Line, {tuple, Line, [ 126 | {atom, Line, pid}, 127 | {call, Line, {atom, Line, pid_to_list}, [ 128 | {call, Line, {atom, Line ,self}, []}]}]}, 129 | {cons, Line, {tuple, Line, [ 130 | {atom, Line, node}, 131 | {call, Line, {atom, Line, node}, []}]}, 132 | %% get the metadata with lager:md(), this will always return a list so we can use it as the tail here 133 | {call, Line, {remote, Line, {atom, Line, lager}, {atom, Line, md}}, []}}}}}}, 134 | %{nil, Line}}}}}}}, 135 | DefaultAttrs = case erlang:get(application) of 136 | undefined -> 137 | DefaultAttrs0; 138 | App -> 139 | %% stick the application in the attribute list 140 | concat_lists({cons, Line, {tuple, Line, [ 141 | {atom, Line, application}, 142 | {atom, Line, App}]}, 143 | {nil, Line}}, DefaultAttrs0) 144 | end, 145 | {Meta, Message, Arguments} = case Arguments0 of 146 | [Format] -> 147 | {DefaultAttrs, Format, {atom, Line, none}}; 148 | [Arg1, Arg2] -> 149 | %% some ambiguity here, figure out if these arguments are 150 | %% [Format, Args] or [Attr, Format]. 151 | %% The trace attributes will be a list of tuples, so check 152 | %% for that. 153 | case {element(1, Arg1), Arg1} of 154 | {_, {cons, _, {tuple, _, _}, _}} -> 155 | {concat_lists(Arg1, DefaultAttrs), 156 | Arg2, {atom, Line, none}}; 157 | {Type, _} when Type == var; 158 | Type == lc; 159 | Type == call; 160 | Type == record_field -> 161 | %% crap, its not a literal. look at the second 162 | %% argument to see if it is a string 163 | case Arg2 of 164 | {string, _, _} -> 165 | {concat_lists(Arg1, DefaultAttrs), 166 | Arg2, {atom, Line, none}}; 167 | _ -> 168 | %% not a string, going to have to guess 169 | %% it's the argument list 170 | {DefaultAttrs, Arg1, Arg2} 171 | end; 172 | _ -> 173 | {DefaultAttrs, Arg1, Arg2} 174 | end; 175 | [Attrs, Format, Args] -> 176 | {concat_lists(Attrs, DefaultAttrs), Format, Args} 177 | end, 178 | %% Generate some unique variable names so we don't accidentally export from case clauses. 179 | %% Note that these are not actual atoms, but the AST treats variable names as atoms. 180 | LevelVar = make_varname("__Level", Line), 181 | TracesVar = make_varname("__Traces", Line), 182 | PidVar = make_varname("__Pid", Line), 183 | LogFun = case Safety of 184 | safe -> 185 | do_log; 186 | unsafe -> 187 | do_log_unsafe 188 | end, 189 | %% Wrap the call to lager:dispatch_log/6 in case that will avoid doing any work if this message is not elegible for logging 190 | %% See lager.erl (lines 89-100) for lager:dispatch_log/6 191 | %% case {whereis(Sink), whereis(?DEFAULT_SINK), lager_config:get({Sink, loglevel}, {?LOG_NONE, []})} of 192 | {'case',Line, 193 | {tuple,Line, 194 | [{call,Line,{atom,Line,whereis},[{atom,Line,SinkName}]}, 195 | {call,Line,{atom,Line,whereis},[{atom,Line,?DEFAULT_SINK}]}, 196 | {call,Line, 197 | {remote,Line,{atom,Line,lager_config},{atom,Line,get}}, 198 | [{tuple,Line,[{atom,Line,SinkName},{atom,Line,loglevel}]}, 199 | {tuple,Line,[{integer,Line,0},{nil,Line}]}]}]}, 200 | %% {undefined, undefined, _} -> {error, lager_not_running}; 201 | [{clause,Line, 202 | [{tuple,Line, 203 | [{atom,Line,undefined},{atom,Line,undefined},{var,Line,'_'}]}], 204 | [], 205 | %% trick the linter into avoiding a 'term constructed but not used' error: 206 | %% (fun() -> {error, lager_not_running} end)() 207 | [{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple, Line, [{atom, Line, error},{atom, Line, lager_not_running}]}]}]}}, []}] 208 | }, 209 | %% {undefined, _, _} -> {error, {sink_not_configured, Sink}}; 210 | {clause,Line, 211 | [{tuple,Line, 212 | [{atom,Line,undefined},{var,Line,'_'},{var,Line,'_'}]}], 213 | [], 214 | %% same trick as above to avoid linter error 215 | [{call, Line, {'fun', Line, {clauses, [{clause, Line, [],[], [{tuple,Line, [{atom,Line,error}, {tuple,Line,[{atom,Line,sink_not_configured},{atom,Line,SinkName}]}]}]}]}}, []}] 216 | }, 217 | %% {SinkPid, _, {Level, Traces}} when ... -> lager:do_log/9; 218 | {clause,Line, 219 | [{tuple,Line, 220 | [{var,Line,PidVar}, 221 | {var,Line,'_'}, 222 | {tuple,Line,[{var,Line,LevelVar},{var,Line,TracesVar}]}]}], 223 | [[{op, Line, 'orelse', 224 | {op, Line, '/=', {op, Line, 'band', {var, Line, LevelVar}, {integer, Line, SeverityAsInt}}, {integer, Line, 0}}, 225 | {op, Line, '/=', {var, Line, TracesVar}, {nil, Line}}}]], 226 | [{call,Line,{remote, Line, {atom, Line, lager}, {atom, Line, LogFun}}, 227 | [{atom,Line,Severity}, 228 | Meta, 229 | Message, 230 | Arguments, 231 | {integer, Line, get(truncation_size)}, 232 | {integer, Line, SeverityAsInt}, 233 | {var, Line, LevelVar}, 234 | {var, Line, TracesVar}, 235 | {atom, Line, SinkName}, 236 | {var, Line, PidVar}]}]}, 237 | %% _ -> ok 238 | {clause,Line,[{var,Line,'_'}],[],[{atom,Line,ok}]}]}. 239 | 240 | make_varname(Prefix, Line) -> 241 | list_to_atom(Prefix ++ atom_to_list(get(module)) ++ integer_to_list(Line)). 242 | 243 | %% concat 2 list ASTs by replacing the terminating [] in A with the contents of B 244 | concat_lists({var, Line, _Name}=Var, B) -> 245 | %% concatenating a var with a cons 246 | {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}}, 247 | [{cons, Line, Var, B}]}; 248 | concat_lists({lc, Line, _Body, _Generator} = LC, B) -> 249 | %% concatenating a LC with a cons 250 | {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}}, 251 | [{cons, Line, LC, B}]}; 252 | concat_lists({call, Line, _Function, _Args} = Call, B) -> 253 | %% concatenating a call with a cons 254 | {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}}, 255 | [{cons, Line, Call, B}]}; 256 | concat_lists({record_field, Line, _Var, _Record, _Field} = Rec, B) -> 257 | %% concatenating a record_field with a cons 258 | {call, Line, {remote, Line, {atom, Line, lists},{atom, Line, flatten}}, 259 | [{cons, Line, Rec, B}]}; 260 | concat_lists({nil, _Line}, B) -> 261 | B; 262 | concat_lists({cons, Line, Element, Tail}, B) -> 263 | {cons, Line, Element, concat_lists(Tail, B)}. 264 | 265 | stash_record(Record) -> 266 | Records = case erlang:get(records) of 267 | undefined -> 268 | []; 269 | R -> 270 | R 271 | end, 272 | erlang:put(records, [Record|Records]). 273 | 274 | insert_record_attribute(AST) -> 275 | lists:foldl(fun({attribute, Line, module, _}=E, Acc) -> 276 | [E, {attribute, Line, lager_records, erlang:get(records)}|Acc]; 277 | (E, Acc) -> 278 | [E|Acc] 279 | end, [], AST). 280 | 281 | guess_application(Dirname, Attr) when Dirname /= undefined -> 282 | case find_app_file(Dirname) of 283 | no_idea -> 284 | %% try it based on source file directory (app.src most likely) 285 | guess_application(undefined, Attr); 286 | _ -> 287 | ok 288 | end; 289 | guess_application(undefined, {attribute, _, file, {Filename, _}}) -> 290 | Dir = filename:dirname(Filename), 291 | find_app_file(Dir); 292 | guess_application(_, _) -> 293 | ok. 294 | 295 | find_app_file(Dir) -> 296 | case filelib:wildcard(Dir++"/*.{app,app.src}") of 297 | [] -> 298 | no_idea; 299 | [File] -> 300 | case file:consult(File) of 301 | {ok, [{application, Appname, _Attributes}|_]} -> 302 | erlang:put(application, Appname); 303 | _ -> 304 | no_idea 305 | end; 306 | _ -> 307 | %% multiple files, uh oh 308 | no_idea 309 | end. 310 | -------------------------------------------------------------------------------- /test/compress_pr_record_test.erl: -------------------------------------------------------------------------------- 1 | -module(compress_pr_record_test). 2 | 3 | -compile([{parse_transform, lager_transform}]). 4 | 5 | -record(a, {field1, field2, foo, bar, baz, zyu, zix}). 6 | 7 | -ifdef(TEST). 8 | -include_lib("eunit/include/eunit.hrl"). 9 | -endif. 10 | 11 | nested_record_test() -> 12 | A = #a{field1 = "Notice me senpai"}, 13 | Pr_A = lager:pr(A, ?MODULE), 14 | Pr_A_Comp = lager:pr(A, ?MODULE, [compress]), 15 | ?assertMatch({'$lager_record', a, [{field1, "Notice me senpai"}, {field2, undefined} | _]}, Pr_A), 16 | ?assertEqual({'$lager_record', a, [{field1, "Notice me senpai"}]}, Pr_A_Comp). 17 | -------------------------------------------------------------------------------- /test/crash.erl: -------------------------------------------------------------------------------- 1 | 2 | %% a module that crashes in just about every way possible 3 | 4 | -module(crash). 5 | 6 | -behaviour(gen_server). 7 | 8 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). 9 | 10 | -export([start/0]). 11 | 12 | -record(state, { 13 | host :: term(), 14 | port :: term() 15 | }). 16 | 17 | start() -> 18 | gen_server:start({local, ?MODULE}, ?MODULE, [], []). 19 | 20 | init(_) -> 21 | {ok, {}}. 22 | 23 | handle_call(undef, _, State) -> 24 | {reply, ?MODULE:booger(), State}; 25 | handle_call(badfun, _, State) -> 26 | M = booger, 27 | {reply, M(), State}; 28 | handle_call(bad_return, _, _) -> 29 | bleh; 30 | handle_call(bad_return_string, _, _) -> 31 | {tuple, {tuple, "string"}}; 32 | handle_call(case_clause, _, State) -> 33 | case State of 34 | goober -> 35 | {reply, ok, State} 36 | end; 37 | handle_call(case_clause_string, _, State) -> 38 | Foo = atom_to_list(?MODULE), 39 | case Foo of 40 | State -> 41 | {reply, ok, State} 42 | end; 43 | handle_call(if_clause, _, State) -> 44 | if State == 1 -> 45 | {reply, ok, State} 46 | end; 47 | handle_call(try_clause, _, State) -> 48 | Res = try tuple_to_list(State) of 49 | [_A, _B] -> ok 50 | catch 51 | _:_ -> ok 52 | end, 53 | {reply, Res, State}; 54 | handle_call(badmatch, _, State) -> 55 | {A, B, C} = State, 56 | {reply, [A, B, C], State}; 57 | handle_call(badrecord, _, State) -> 58 | Host = State#state.host, 59 | {reply, Host, State}; 60 | handle_call(function_clause, _, State) -> 61 | {reply, function(State), State}; 62 | handle_call(badarith, _, State) -> 63 | Res = 1 / length(tuple_to_list(State)), 64 | {reply, Res, State}; 65 | handle_call(badarg1, _, State) -> 66 | Res = list_to_binary(["foo", bar]), 67 | {reply, Res, State}; 68 | handle_call(badarg2, _, State) -> 69 | Res = erlang:iolist_to_binary(["foo", bar]), 70 | {reply, Res, State}; 71 | handle_call(system_limit, _, State) -> 72 | Res = list_to_atom(lists:flatten(lists:duplicate(256, "a"))), 73 | {reply, Res, State}; 74 | handle_call(process_limit, _, State) -> 75 | %% run with +P 300 to make this crash 76 | [erlang:spawn(fun() -> timer:sleep(5000) end) || _ <- lists:seq(0, 500)], 77 | {reply, ok, State}; 78 | handle_call(port_limit, _, State) -> 79 | [erlang:open_port({spawn, "ls"}, []) || _ <- lists:seq(0, 1024)], 80 | {reply, ok, State}; 81 | handle_call(noproc, _, State) -> 82 | Res = gen_event:call(foo, bar, baz), 83 | {reply, Res, State}; 84 | handle_call(badarity, _, State) -> 85 | F = fun(A, B, C) -> A + B + C end, 86 | Res = F(State), 87 | {reply, Res, State}; 88 | handle_call(throw, _, _State) -> 89 | throw(a_ball); 90 | handle_call(_Call, _From, State) -> 91 | {reply, ok, State}. 92 | 93 | handle_cast(_Cast, State) -> 94 | {noreply, State}. 95 | 96 | handle_info(_Info, State) -> 97 | {noreply, State}. 98 | 99 | terminate(_, _) -> 100 | ok. 101 | 102 | code_change(_, State, _) -> 103 | {ok, State}. 104 | 105 | function(X) when is_list(X) -> 106 | ok. 107 | -------------------------------------------------------------------------------- /test/lager_app_tests.erl: -------------------------------------------------------------------------------- 1 | -module(lager_app_tests). 2 | 3 | -compile([{parse_transform, lager_transform}]). 4 | 5 | -include_lib("eunit/include/eunit.hrl"). 6 | 7 | 8 | get_env_default_test() -> 9 | ?assertEqual(<<"Some">>, lager_app:get_env_default(undefined, <<"Some">>)), 10 | ?assertEqual(<<"Value">>, lager_app:get_env_default({ok, <<"Value">>}, <<"Some">>)), 11 | ok. 12 | 13 | get_env_test() -> 14 | application:set_env(myapp, mykey1, <<"Value">>), 15 | 16 | ?assertEqual(<<"Some">>, lager_app:get_env(myapp, mykey0, <<"Some">>)), 17 | ?assertEqual(<<"Value">>, lager_app:get_env(myapp, mykey1, <<"Some">>)), 18 | 19 | ?assertEqual(undefined, lager_app:get_env(myapp, mykey0)), 20 | ?assertEqual(<<"Value">>, lager_app:get_env(myapp, mykey1)), 21 | ok. 22 | 23 | -------------------------------------------------------------------------------- /test/lager_crash_backend.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. 2 | %% 3 | %% This file is provided to you under the Apache License, 4 | %% Version 2.0 (the "License"); you may not use this file 5 | %% except in compliance with the License. You may obtain 6 | %% a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, 11 | %% software distributed under the License is distributed on an 12 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | %% KIND, either express or implied. See the License for the 14 | %% specific language governing permissions and limitations 15 | %% under the License. 16 | 17 | -module(lager_crash_backend). 18 | 19 | -include("lager.hrl"). 20 | 21 | -behaviour(gen_event). 22 | 23 | -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, 24 | code_change/3]). 25 | 26 | -ifdef(TEST). 27 | -include_lib("eunit/include/eunit.hrl"). 28 | -endif. 29 | 30 | init([CrashBefore, CrashAfter]) -> 31 | case is_tuple(CrashBefore) andalso (timer:now_diff(CrashBefore, os:timestamp()) > 0) of 32 | true -> 33 | %?debugFmt("crashing!~n", []), 34 | {error, crashed}; 35 | _ -> 36 | %?debugFmt("Not crashing!~n", []), 37 | case is_tuple(CrashAfter) of 38 | true -> 39 | CrashTime = timer:now_diff(CrashAfter, os:timestamp()) div 1000, 40 | case CrashTime > 0 of 41 | true -> 42 | %?debugFmt("crashing in ~p~n", [CrashTime]), 43 | erlang:send_after(CrashTime, self(), crash), 44 | {ok, {}}; 45 | _ -> {error, crashed} 46 | end; 47 | _ -> 48 | {ok, {}} 49 | end 50 | end. 51 | 52 | handle_call(_Request, State) -> 53 | {ok, ok, State}. 54 | 55 | handle_event(_Event, State) -> 56 | {ok, State}. 57 | 58 | handle_info(crash, _State) -> 59 | %?debugFmt("Time to crash!~n", []), 60 | crash; 61 | handle_info(_Info, State) -> 62 | {ok, State}. 63 | 64 | terminate(_Reason, _State) -> 65 | ok. 66 | 67 | code_change(_OldVsn, State, _Extra) -> 68 | {ok, State}. 69 | -------------------------------------------------------------------------------- /test/lager_manager_killer_test.erl: -------------------------------------------------------------------------------- 1 | -module(lager_manager_killer_test). 2 | -author("Sungjin Park "). 3 | 4 | -compile([{parse_transform, lager_transform}]). 5 | 6 | -ifdef(TEST). 7 | -include_lib("eunit/include/eunit.hrl"). 8 | 9 | -define(TEST_SINK_NAME, '__lager_test_sink'). %% <-- used by parse transform 10 | -define(TEST_SINK_EVENT, '__lager_test_sink_lager_event'). %% <-- used by lager API calls and internals for gen_event 11 | 12 | overload_test_() -> 13 | {timeout, 60, 14 | fun() -> 15 | application:stop(lager), 16 | application:load(lager), 17 | Delay = 1000, % sleep 1 sec on every log 18 | KillerHWM = 10, % kill the manager if there are more than 10 pending logs 19 | KillerReinstallAfter = 1000, % reinstall killer after 1 sec 20 | application:set_env(lager, handlers, [{lager_slow_backend, [{delay, Delay}]}]), 21 | application:set_env(lager, async_threshold, undefined), 22 | application:set_env(lager, error_logger_redirect, true), 23 | application:set_env(lager, killer_hwm, KillerHWM), 24 | application:set_env(lager, killer_reinstall_after, KillerReinstallAfter), 25 | ensure_started(lager), 26 | lager_config:set(async, true), 27 | Manager = whereis(lager_event), 28 | erlang:trace(all, true, [procs]), 29 | [lager:info("~p'th message", [N]) || N <- lists:seq(1,KillerHWM+2)], 30 | Margin = 100, 31 | ok = confirm_manager_exit(Manager, Delay+Margin), 32 | ok = confirm_sink_reregister(lager_event, Margin), 33 | erlang:trace(all, false, [procs]), 34 | wait_until(fun() -> 35 | case proplists:get_value(lager_manager_killer, gen_event:which_handlers(lager_event)) of 36 | [] -> false; 37 | _ -> true 38 | end 39 | end, Margin, 15), 40 | wait_until(fun() -> 41 | case gen_event:call(lager_event, lager_manager_killer, get_settings) of 42 | [KillerHWM, KillerReinstallAfter] -> true; 43 | _Other -> false 44 | end 45 | end, Margin, 15), 46 | application:stop(lager) 47 | end}. 48 | 49 | overload_alternate_sink_test_() -> 50 | {timeout, 60, 51 | fun() -> 52 | application:stop(lager), 53 | application:load(lager), 54 | Delay = 1000, % sleep 1 sec on every log 55 | KillerHWM = 10, % kill the manager if there are more than 10 pending logs 56 | KillerReinstallAfter = 1000, % reinstall killer after 1 sec 57 | application:set_env(lager, handlers, []), 58 | application:set_env(lager, extra_sinks, [{?TEST_SINK_EVENT, [ 59 | {handlers, [{lager_slow_backend, [{delay, Delay}]}]}, 60 | {killer_hwm, KillerHWM}, 61 | {killer_reinstall_after, KillerReinstallAfter}, 62 | {async_threshold, undefined} 63 | ]}]), 64 | application:set_env(lager, error_logger_redirect, true), 65 | ensure_started(lager), 66 | lager_config:set({?TEST_SINK_EVENT, async}, true), 67 | Manager = whereis(?TEST_SINK_EVENT), 68 | erlang:trace(all, true, [procs]), 69 | [?TEST_SINK_NAME:info("~p'th message", [N]) || N <- lists:seq(1,KillerHWM+2)], 70 | Margin = 100, 71 | ok = confirm_manager_exit(Manager, Delay+Margin), 72 | ok = confirm_sink_reregister(?TEST_SINK_EVENT, Margin), 73 | erlang:trace(all, false, [procs]), 74 | wait_until(fun() -> 75 | case proplists:get_value(lager_manager_killer, gen_event:which_handlers(?TEST_SINK_EVENT)) of 76 | [] -> false; 77 | _ -> true 78 | end 79 | end, Margin, 15), 80 | wait_until(fun() -> 81 | case gen_event:call(?TEST_SINK_EVENT, lager_manager_killer, get_settings) of 82 | [KillerHWM, KillerReinstallAfter] -> true; 83 | _Other -> false 84 | end 85 | end, Margin, 15), 86 | application:stop(lager) 87 | end}. 88 | 89 | ensure_started(App) -> 90 | case application:start(App) of 91 | ok -> 92 | ok; 93 | {error, {not_started, Dep}} -> 94 | ensure_started(Dep), 95 | ensure_started(App) 96 | end. 97 | 98 | confirm_manager_exit(Manager, Delay) -> 99 | receive 100 | {trace, Manager, exit, killed} -> 101 | ?debugFmt("Manager ~p killed", [Manager]); 102 | Other -> 103 | ?debugFmt("OTHER MSG: ~p", [Other]), 104 | confirm_manager_exit(Manager, Delay) 105 | after Delay -> 106 | ?assert(false) 107 | end. 108 | 109 | confirm_sink_reregister(Sink, Delay) -> 110 | receive 111 | {trace, _Pid, register, Sink} -> 112 | ?assertNot(lists:member(lager_manager_killer, gen_event:which_handlers(Sink))) 113 | after Delay -> 114 | ?assert(false) 115 | end. 116 | 117 | wait_until(_Fun, _Delay, 0) -> 118 | {error, too_many_retries}; 119 | wait_until(Fun, Delay, Retries) -> 120 | case Fun() of 121 | true -> ok; 122 | false -> timer:sleep(Delay), wait_until(Fun, Delay, Retries-1) 123 | end. 124 | 125 | -endif. 126 | -------------------------------------------------------------------------------- /test/lager_rotate.erl: -------------------------------------------------------------------------------- 1 | -module(lager_rotate). 2 | 3 | -compile(export_all). 4 | 5 | -ifdef(TEST). 6 | -include_lib("eunit/include/eunit.hrl"). 7 | -endif. 8 | 9 | 10 | rotate_test_() -> 11 | {foreach, 12 | fun() -> 13 | file:write_file("test1.log", ""), 14 | file:write_file("test2.log", ""), 15 | file:write_file("test3.log", ""), 16 | file:delete("test1.log.0"), 17 | file:delete("test2.log.0"), 18 | file:delete("test3.log.0"), 19 | error_logger:tty(false), 20 | application:load(lager), 21 | application:set_env(lager, handlers, 22 | [{lager_file_backend, [{file, "test1.log"}, {level, info}]}, 23 | {lager_file_backend, [{file, "test2.log"}, {level, info}]}]), 24 | application:set_env(lager, extra_sinks, 25 | [{sink_event, 26 | [{handlers, 27 | [{lager_file_backend, [{file, "test3.log"}, {level, info}]}]} 28 | ]}]), 29 | application:set_env(lager, error_logger_redirect, false), 30 | application:set_env(lager, async_threshold, undefined), 31 | lager:start() 32 | end, 33 | fun(_) -> 34 | file:delete("test1.log"), 35 | file:delete("test2.log"), 36 | file:delete("test3.log"), 37 | file:delete("test1.log.0"), 38 | file:delete("test2.log.0"), 39 | file:delete("test3.log.0"), 40 | application:stop(lager), 41 | application:stop(goldrush), 42 | error_logger:tty(true) 43 | end, 44 | [{"Rotate single file", 45 | fun() -> 46 | lager:log(error, self(), "Test message 1"), 47 | lager:log(sink_event, error, self(), "Sink test message 1", []), 48 | lager:rotate_handler({lager_file_backend, "test1.log"}), 49 | ok = wait_until(fun() -> filelib:is_regular("test1.log.0") end, 10), 50 | lager:log(error, self(), "Test message 2"), 51 | lager:log(sink_event, error, self(), "Sink test message 2", []), 52 | 53 | {ok, File1} = file:read_file("test1.log"), 54 | {ok, File2} = file:read_file("test2.log"), 55 | {ok, SinkFile} = file:read_file("test3.log"), 56 | {ok, File1Old} = file:read_file("test1.log.0"), 57 | 58 | have_no_log(File1, <<"Test message 1">>), 59 | have_log(File1, <<"Test message 2">>), 60 | 61 | have_log(File2, <<"Test message 1">>), 62 | have_log(File2, <<"Test message 2">>), 63 | 64 | have_log(File1Old, <<"Test message 1">>), 65 | have_no_log(File1Old, <<"Test message 2">>), 66 | 67 | have_log(SinkFile, <<"Sink test message 1">>), 68 | have_log(SinkFile, <<"Sink test message 2">>) 69 | end}, 70 | {"Rotate sink", 71 | fun() -> 72 | lager:log(error, self(), "Test message 1"), 73 | lager:log(sink_event, error, self(), "Sink test message 1", []), 74 | lager:rotate_sink(sink_event), 75 | ok = wait_until(fun() -> filelib:is_regular("test3.log.0") end, 10), 76 | lager:log(error, self(), "Test message 2"), 77 | lager:log(sink_event, error, self(), "Sink test message 2", []), 78 | {ok, File1} = file:read_file("test1.log"), 79 | {ok, File2} = file:read_file("test2.log"), 80 | {ok, SinkFile} = file:read_file("test3.log"), 81 | {ok, SinkFileOld} = file:read_file("test3.log.0"), 82 | 83 | have_log(File1, <<"Test message 1">>), 84 | have_log(File1, <<"Test message 2">>), 85 | 86 | have_log(File2, <<"Test message 1">>), 87 | have_log(File2, <<"Test message 2">>), 88 | 89 | have_log(SinkFileOld, <<"Sink test message 1">>), 90 | have_no_log(SinkFileOld, <<"Sink test message 2">>), 91 | 92 | have_no_log(SinkFile, <<"Sink test message 1">>), 93 | have_log(SinkFile, <<"Sink test message 2">>) 94 | end}, 95 | {"Rotate all", 96 | fun() -> 97 | lager:log(error, self(), "Test message 1"), 98 | lager:log(sink_event, error, self(), "Sink test message 1", []), 99 | lager:rotate_all(), 100 | ok = wait_until(fun() -> filelib:is_regular("test3.log.0") end, 10), 101 | lager:log(error, self(), "Test message 2"), 102 | lager:log(sink_event, error, self(), "Sink test message 2", []), 103 | {ok, File1} = file:read_file("test1.log"), 104 | {ok, File2} = file:read_file("test2.log"), 105 | {ok, SinkFile} = file:read_file("test3.log"), 106 | {ok, File1Old} = file:read_file("test1.log.0"), 107 | {ok, File2Old} = file:read_file("test2.log.0"), 108 | {ok, SinkFileOld} = file:read_file("test3.log.0"), 109 | 110 | have_no_log(File1, <<"Test message 1">>), 111 | have_log(File1, <<"Test message 2">>), 112 | 113 | have_no_log(File2, <<"Test message 1">>), 114 | have_log(File2, <<"Test message 2">>), 115 | 116 | have_no_log(SinkFile, <<"Sink test message 1">>), 117 | have_log(SinkFile, <<"Sink test message 2">>), 118 | 119 | have_log(SinkFileOld, <<"Sink test message 1">>), 120 | have_no_log(SinkFileOld, <<"Sink test message 2">>), 121 | 122 | have_log(File1Old, <<"Test message 1">>), 123 | have_no_log(File1Old, <<"Test message 2">>), 124 | 125 | have_log(File2Old, <<"Test message 1">>), 126 | have_no_log(File2Old, <<"Test message 2">>) 127 | 128 | end}]}. 129 | 130 | have_log(Data, Log) -> 131 | {_,_} = binary:match(Data, Log). 132 | 133 | have_no_log(Data, Log) -> 134 | nomatch = binary:match(Data, Log). 135 | 136 | wait_until(_Fun, 0) -> {error, too_many_retries}; 137 | wait_until(Fun, Retry) -> 138 | case Fun() of 139 | true -> ok; 140 | false -> 141 | timer:sleep(500), 142 | wait_until(Fun, Retry-1) 143 | end. 144 | -------------------------------------------------------------------------------- /test/lager_slow_backend.erl: -------------------------------------------------------------------------------- 1 | -module(lager_slow_backend). 2 | -author("Sungjin Park "). 3 | -behavior(gen_event). 4 | 5 | -export([init/1, handle_call/2, handle_event/2, handle_info/2, terminate/2, code_change/3]). 6 | 7 | -include("lager.hrl"). 8 | 9 | -record(state, { 10 | delay :: non_neg_integer() 11 | }). 12 | 13 | init([{delay, Delay}]) -> 14 | {ok, #state{delay=Delay}}. 15 | 16 | handle_call(get_loglevel, State) -> 17 | {ok, lager_util:config_to_mask(debug), State}; 18 | handle_call(_Request, State) -> 19 | {ok, ok, State}. 20 | 21 | handle_event({log, _Message}, State) -> 22 | timer:sleep(State#state.delay), 23 | {ok, State}; 24 | handle_event(_Event, State) -> 25 | {ok, State}. 26 | 27 | handle_info(_Info, State) -> 28 | {ok, State}. 29 | 30 | terminate(_Reason, _State) -> 31 | ok. 32 | 33 | code_change(_OldVsn, State, _Extra) -> 34 | {ok, State}. 35 | -------------------------------------------------------------------------------- /test/pr_nested_record_test.erl: -------------------------------------------------------------------------------- 1 | -module(pr_nested_record_test). 2 | 3 | -compile([{parse_transform, lager_transform}]). 4 | 5 | -record(a, {field1 :: term(), field2 :: term()}). 6 | -record(b, {field1 :: term() , field2 :: term()}). 7 | 8 | 9 | -include_lib("eunit/include/eunit.hrl"). 10 | 11 | nested_record_test() -> 12 | A = #a{field1 = x, field2 = y}, 13 | B = #b{field1 = A, field2 = {}}, 14 | Pr_B = lager:pr(B, ?MODULE), 15 | ?assertEqual({'$lager_record', b, 16 | [{field1, {'$lager_record', a, 17 | [{field1, x},{field2, y}]}}, 18 | {field2, {}}]}, 19 | Pr_B). 20 | -------------------------------------------------------------------------------- /test/pr_stacktrace_test.erl: -------------------------------------------------------------------------------- 1 | -module(pr_stacktrace_test). 2 | 3 | -compile([{parse_transform, lager_transform}]). 4 | 5 | -include_lib("eunit/include/eunit.hrl"). 6 | 7 | make_throw() -> 8 | throw({test, exception}). 9 | 10 | bad_arity() -> 11 | lists:concat([], []). 12 | 13 | bad_arg() -> 14 | integer_to_list(1.0). 15 | 16 | pr_stacktrace_throw_test() -> 17 | Result = try 18 | make_throw() 19 | catch 20 | Class:Reason -> 21 | lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason}) 22 | end, 23 | ExpectedPart = " 24 | pr_stacktrace_test:pr_stacktrace_throw_test/0 line 18 25 | pr_stacktrace_test:make_throw/0 line 8 26 | throw:{test,exception}", 27 | ?assertNotEqual(0, string:str(Result, ExpectedPart)). 28 | 29 | 30 | pr_stacktrace_bad_arg_test() -> 31 | Result = try 32 | bad_arg() 33 | catch 34 | Class:Reason -> 35 | lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason}) 36 | end, 37 | ExpectedPart = " 38 | pr_stacktrace_test:pr_stacktrace_bad_arg_test/0 line 32 39 | pr_stacktrace_test:bad_arg/0 line 14 40 | error:badarg", 41 | ?assertNotEqual(0, string:str(Result, ExpectedPart)). 42 | 43 | 44 | pr_stacktrace_bad_arity_test() -> 45 | Result = try 46 | bad_arity() 47 | catch 48 | Class:Reason -> 49 | lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason}) 50 | end, 51 | ExpectedPart = " 52 | pr_stacktrace_test:pr_stacktrace_bad_arity_test/0 line 46 53 | lists:concat([], []) 54 | error:undef", 55 | ?assertNotEqual(0, string:str(Result, ExpectedPart)). -------------------------------------------------------------------------------- /test/special_process.erl: -------------------------------------------------------------------------------- 1 | -module(special_process). 2 | -export([start/0, init/1]). 3 | 4 | start() -> 5 | proc_lib:start_link(?MODULE, init, [self()]). 6 | 7 | init(Parent) -> 8 | proc_lib:init_ack(Parent, {ok, self()}), 9 | loop(). 10 | 11 | loop() -> 12 | receive 13 | function_clause -> 14 | foo(bar), 15 | loop(); 16 | exit -> 17 | exit(byebye), 18 | loop(); 19 | error -> 20 | erlang:error(mybad), 21 | loop(); 22 | {case_clause, X} -> 23 | case X of 24 | notgonnamatch -> 25 | ok; 26 | notthiseither -> 27 | error 28 | end, 29 | loop(); 30 | _ -> 31 | loop() 32 | end. 33 | 34 | foo(baz) -> 35 | ok. 36 | 37 | -------------------------------------------------------------------------------- /test/sync_error_logger.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 1996-2009. All Rights Reserved. 5 | %% 6 | %% The contents of this file are subject to the Erlang Public License, 7 | %% Version 1.1, (the "License"); you may not use this file except in 8 | %% compliance with the License. You should have received a copy of the 9 | %% Erlang Public License along with this software. If not, it can be 10 | %% retrieved online at http://www.erlang.org/. 11 | %% 12 | %% Software distributed under the License is distributed on an "AS IS" 13 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 14 | %% the License for the specific language governing rights and limitations 15 | %% under the License. 16 | %% 17 | %% %CopyrightEnd% 18 | %% 19 | -module(sync_error_logger). 20 | 21 | %% The error_logger API, but synchronous! 22 | %% This is helpful for tests, otherwise you need lots of nasty timer:sleep. 23 | %% Additionally, the warning map can be set on a per-process level, for 24 | %% convienience, via the process dictionary value `warning_map'. 25 | 26 | -export([ 27 | info_msg/1, info_msg/2, 28 | warning_msg/1, warning_msg/2, 29 | error_msg/1,error_msg/2 30 | ]). 31 | 32 | -export([ 33 | info_report/1, info_report/2, 34 | warning_report/1, warning_report/2, 35 | error_report/1, error_report/2 36 | ]). 37 | 38 | info_msg(Format) -> 39 | info_msg(Format, []). 40 | 41 | info_msg(Format, Args) -> 42 | gen_event:sync_notify(error_logger, {info_msg, group_leader(), {self(), Format, Args}}). 43 | 44 | warning_msg(Format) -> 45 | warning_msg(Format, []). 46 | 47 | warning_msg(Format, Args) -> 48 | gen_event:sync_notify(error_logger, {warning_msg_tag(), group_leader(), {self(), Format, Args}}). 49 | 50 | error_msg(Format) -> 51 | error_msg(Format, []). 52 | 53 | error_msg(Format, Args) -> 54 | gen_event:sync_notify(error_logger, {error, group_leader(), {self(), Format, Args}}). 55 | 56 | info_report(Report) -> 57 | info_report(std_info, Report). 58 | 59 | info_report(Type, Report) -> 60 | gen_event:sync_notify(error_logger, {info_report, group_leader(), {self(), Type, Report}}). 61 | 62 | warning_report(Report) -> 63 | warning_report(std_warning, Report). 64 | 65 | warning_report(Type, Report) -> 66 | {Tag, NType} = warning_report_tag(Type), 67 | gen_event:sync_notify(error_logger, {Tag, group_leader(), {self(), NType, Report}}). 68 | 69 | error_report(Report) -> 70 | error_report(std_error, Report). 71 | 72 | error_report(Type, Report) -> 73 | gen_event:sync_notify(error_logger, {error_report, group_leader(), {self(), Type, Report}}). 74 | 75 | warning_msg_tag() -> 76 | case get(warning_map) of 77 | warning -> warning_msg; 78 | info -> info_msg; 79 | _ -> error 80 | end. 81 | 82 | warning_report_tag(Type) -> 83 | case {get(warning_map), Type == std_warning} of 84 | {warning, _} -> {warning_report, Type}; 85 | {info, true} -> {info_report, std_info}; 86 | {info, false} -> {info_report, Type}; 87 | {_, true} -> {error_report, std_error}; 88 | {_, false} -> {error_report, Type} 89 | end. 90 | -------------------------------------------------------------------------------- /test/trunc_io_eqc.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% trunc_io_eqc: QuickCheck test for trunc_io:format with maxlen 4 | %% 5 | %% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved. 6 | %% 7 | %% This file is provided to you under the Apache License, 8 | %% Version 2.0 (the "License"); you may not use this file 9 | %% except in compliance with the License. You may obtain 10 | %% a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, 15 | %% software distributed under the License is distributed on an 16 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | %% KIND, either express or implied. See the License for the 18 | %% specific language governing permissions and limitations 19 | %% under the License. 20 | %% 21 | %% ------------------------------------------------------------------- 22 | -module(trunc_io_eqc). 23 | 24 | -ifdef(TEST). 25 | -ifdef(EQC). 26 | -export([test/0, test/1, check/0, prop_format/0, prop_equivalence/0]). 27 | 28 | -include_lib("eqc/include/eqc.hrl"). 29 | -include_lib("eunit/include/eunit.hrl"). 30 | 31 | -define(QC_OUT(P), 32 | eqc:on_output(fun(Str, Args) -> io:format(user, Str, Args) end, P)). 33 | 34 | %%==================================================================== 35 | %% eunit test 36 | %%==================================================================== 37 | 38 | eqc_test_() -> 39 | {timeout, 60, 40 | {spawn, 41 | [ 42 | {timeout, 30, ?_assertEqual(true, eqc:quickcheck(eqc:testing_time(14, ?QC_OUT(prop_format()))))}, 43 | {timeout, 30, ?_assertEqual(true, eqc:quickcheck(eqc:testing_time(14, ?QC_OUT(prop_equivalence()))))} 44 | ] 45 | }}. 46 | 47 | %%==================================================================== 48 | %% Shell helpers 49 | %%==================================================================== 50 | 51 | test() -> 52 | test(100). 53 | 54 | test(N) -> 55 | quickcheck(numtests(N, prop_format())). 56 | 57 | check() -> 58 | check(prop_format(), current_counterexample()). 59 | 60 | %%==================================================================== 61 | %% Generators 62 | %%==================================================================== 63 | 64 | gen_fmt_args() -> 65 | list(oneof([gen_print_str(), 66 | "~~", 67 | {"~10000000.p", gen_any(5)}, 68 | {"~w", gen_any(5)}, 69 | {"~s", oneof([gen_print_str(), gen_atom(), gen_quoted_atom(), gen_print_bin(), gen_iolist(5)])}, 70 | {"~1000000.P", gen_any(5), 4}, 71 | {"~W", gen_any(5), 4}, 72 | {"~i", gen_any(5)}, 73 | {"~B", nat()}, 74 | {"~b", nat()}, 75 | {"~X", nat(), "0x"}, 76 | {"~x", nat(), "0x"}, 77 | {"~.10#", nat()}, 78 | {"~.10+", nat()}, 79 | {"~.36B", nat()}, 80 | {"~1000000.62P", gen_any(5), 4}, 81 | {"~c", gen_char()}, 82 | {"~tc", gen_char()}, 83 | {"~f", real()}, 84 | {"~10.f", real()}, 85 | {"~g", real()}, 86 | {"~10.g", real()}, 87 | {"~e", real()}, 88 | {"~10.e", real()} 89 | ])). 90 | 91 | 92 | %% Generates a printable string 93 | gen_print_str() -> 94 | ?LET(Xs, list(char()), [X || X <- Xs, io_lib:printable_list([X]), X /= $~, X < 256]). 95 | 96 | gen_print_bin() -> 97 | ?LET(Xs, gen_print_str(), list_to_binary(Xs)). 98 | 99 | gen_any(MaxDepth) -> 100 | oneof([largeint(), 101 | gen_atom(), 102 | gen_quoted_atom(), 103 | nat(), 104 | %real(), 105 | binary(), 106 | gen_bitstring(), 107 | gen_pid(), 108 | gen_port(), 109 | gen_ref(), 110 | gen_fun()] ++ 111 | [?LAZY(list(gen_any(MaxDepth - 1))) || MaxDepth /= 0] ++ 112 | [?LAZY(gen_tuple(gen_any(MaxDepth - 1))) || MaxDepth /= 0]). 113 | 114 | gen_iolist(0) -> 115 | []; 116 | gen_iolist(Depth) -> 117 | list(oneof([gen_char(), gen_print_str(), gen_print_bin(), gen_iolist(Depth-1)])). 118 | 119 | gen_atom() -> 120 | elements([abc, def, ghi]). 121 | 122 | gen_quoted_atom() -> 123 | elements(['abc@bar', '@bar', '10gen']). 124 | 125 | gen_bitstring() -> 126 | ?LET(XS, binary(), <>). 127 | 128 | gen_tuple(Gen) -> 129 | ?LET(Xs, list(Gen), list_to_tuple(Xs)). 130 | 131 | gen_max_len() -> %% Generate length from 3 to whatever. Needs space for ... in output 132 | ?LET(Xs, int(), 3 + abs(Xs)). 133 | 134 | gen_pid() -> 135 | ?LAZY(spawn(fun() -> ok end)). 136 | 137 | gen_port() -> 138 | ?LAZY(begin 139 | Port = erlang:open_port({spawn, "true"}, []), 140 | catch(erlang:port_close(Port)), 141 | Port 142 | end). 143 | 144 | gen_ref() -> 145 | ?LAZY(make_ref()). 146 | 147 | gen_fun() -> 148 | ?LAZY(fun() -> ok end). 149 | 150 | gen_char() -> 151 | oneof(lists:seq($A, $z)). 152 | 153 | %%==================================================================== 154 | %% Property 155 | %%==================================================================== 156 | 157 | %% Checks that trunc_io:format produces output less than or equal to MaxLen 158 | prop_format() -> 159 | ?FORALL({FmtArgs, MaxLen}, {gen_fmt_args(), gen_max_len()}, 160 | begin 161 | %% Because trunc_io will print '...' when its running out of 162 | %% space, even if the remaining space is less than 3, it 163 | %% doesn't *exactly* stick to the specified limit. 164 | 165 | %% Also, since we don't truncate terms not printed with 166 | %% ~p/~P/~w/~W/~s, we also need to calculate the wiggle room 167 | %% for those. Hence the fudge factor calculated below. 168 | FudgeLen = calculate_fudge(FmtArgs, 50), 169 | {FmtStr, Args} = build_fmt_args(FmtArgs), 170 | try 171 | Str = lists:flatten(lager_trunc_io:format(FmtStr, Args, MaxLen)), 172 | ?WHENFAIL(begin 173 | io:format(user, "FmtStr: ~p\n", [FmtStr]), 174 | io:format(user, "Args: ~p\n", [Args]), 175 | io:format(user, "FudgeLen: ~p\n", [FudgeLen]), 176 | io:format(user, "MaxLen: ~p\n", [MaxLen]), 177 | io:format(user, "ActLen: ~p\n", [length(Str)]), 178 | io:format(user, "Str: ~p\n", [Str]) 179 | end, 180 | %% Make sure the result is a printable list 181 | %% and if the format string is less than the length, 182 | %% the result string is less than the length. 183 | conjunction([{printable, Str == "" orelse 184 | io_lib:printable_list(Str)}, 185 | {length, length(FmtStr) > MaxLen orelse 186 | length(Str) =< MaxLen + FudgeLen}])) 187 | catch 188 | _:Err -> 189 | io:format(user, "\nException: ~p\n", [Err]), 190 | io:format(user, "FmtStr: ~p\n", [FmtStr]), 191 | io:format(user, "Args: ~p\n", [Args]), 192 | false 193 | end 194 | end). 195 | 196 | %% Checks for equivalent formatting to io_lib 197 | prop_equivalence() -> 198 | ?FORALL(FmtArgs, gen_fmt_args(), 199 | begin 200 | {FmtStr, Args} = build_fmt_args(FmtArgs), 201 | Expected = lists:flatten(io_lib:format(FmtStr, Args)), 202 | Actual = lists:flatten(lager_trunc_io:format(FmtStr, Args, 10485760)), 203 | ?WHENFAIL(begin 204 | io:format(user, "FmtStr: ~p\n", [FmtStr]), 205 | io:format(user, "Args: ~p\n", [Args]), 206 | io:format(user, "Expected: ~p\n", [Expected]), 207 | io:format(user, "Actual: ~p\n", [Actual]) 208 | end, 209 | Expected == Actual) 210 | end). 211 | 212 | 213 | %%==================================================================== 214 | %% Internal helpers 215 | %%==================================================================== 216 | 217 | %% Build a tuple of {Fmt, Args} from a gen_fmt_args() return 218 | build_fmt_args(FmtArgs) -> 219 | F = fun({Fmt, Arg}, {FmtStr0, Args0}) -> 220 | {FmtStr0 ++ Fmt, Args0 ++ [Arg]}; 221 | ({Fmt, Arg1, Arg2}, {FmtStr0, Args0}) -> 222 | {FmtStr0 ++ Fmt, Args0 ++ [Arg1, Arg2]}; 223 | (Str, {FmtStr0, Args0}) -> 224 | {FmtStr0 ++ Str, Args0} 225 | end, 226 | lists:foldl(F, {"", []}, FmtArgs). 227 | 228 | calculate_fudge([], Acc) -> 229 | Acc; 230 | calculate_fudge([{"~62P", _Arg, _Depth}|T], Acc) -> 231 | calculate_fudge(T, Acc+62); 232 | calculate_fudge([{Fmt, Arg}|T], Acc) when 233 | Fmt == "~f"; Fmt == "~10.f"; 234 | Fmt == "~g"; Fmt == "~10.g"; 235 | Fmt == "~e"; Fmt == "~10.e"; 236 | Fmt == "~x"; Fmt == "~X"; 237 | Fmt == "~B"; Fmt == "~b"; Fmt == "~36B"; 238 | Fmt == "~.10#"; Fmt == "~10+" -> 239 | calculate_fudge(T, Acc + length(lists:flatten(io_lib:format(Fmt, [Arg])))); 240 | calculate_fudge([_|T], Acc) -> 241 | calculate_fudge(T, Acc). 242 | 243 | -endif. % (EQC). 244 | -endif. % (TEST). 245 | -------------------------------------------------------------------------------- /test/zzzz_gh280_crash.erl: -------------------------------------------------------------------------------- 1 | %% @doc This test is named zzzz_gh280_crash because it has to be run first and tests are run in 2 | %% reverse alphabetical order. 3 | %% 4 | %% The problem we are attempting to detect here is when log_mf_h is installed as a handler for error_logger 5 | %% and lager starts up to replace the current handlers with its own. This causes a start up crash because 6 | %% OTP error logging modules do not have any notion of a lager-style log level. 7 | -module(zzzz_gh280_crash). 8 | -compile(export_all). 9 | 10 | -include_lib("eunit/include/eunit.hrl"). 11 | 12 | gh280_crash_test() -> 13 | {timeout, 30, fun() -> gh280_impl() end}. 14 | 15 | gh280_impl() -> 16 | application:stop(lager), 17 | application:stop(goldrush), 18 | 19 | error_logger:tty(false), 20 | %% see https://github.com/erlang/otp/blob/maint/lib/stdlib/src/log_mf_h.erl#L81 21 | %% for an explanation of the init arguments to log_mf_h 22 | ok = gen_event:add_sup_handler(error_logger, log_mf_h, log_mf_h:init("/tmp", 10000, 5)), 23 | lager:start(), 24 | Result = receive 25 | {gen_event_EXIT,log_mf_h,normal} -> 26 | true; 27 | {gen_event_EXIT,Handler,Reason} -> 28 | {Handler,Reason}; 29 | X -> 30 | X 31 | after 10000 -> 32 | timeout 33 | end, 34 | ?assert(Result), 35 | application:stop(lager), 36 | application:stop(goldrush). 37 | -------------------------------------------------------------------------------- /tools.mk: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------- 2 | # 3 | # Copyright (c) 2014 Basho Technologies, Inc. 4 | # 5 | # This file is provided to you under the Apache License, 6 | # Version 2.0 (the "License"); you may not use this file 7 | # except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, 13 | # software distributed under the License is distributed on an 14 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | # KIND, either express or implied. See the License for the 16 | # specific language governing permissions and limitations 17 | # under the License. 18 | # 19 | # ------------------------------------------------------------------- 20 | 21 | # ------------------------------------------------------------------- 22 | # NOTE: This file is is from https://github.com/basho/tools.mk. 23 | # It should not be edited in a project. It should simply be updated 24 | # wholesale when a new version of tools.mk is released. 25 | # ------------------------------------------------------------------- 26 | 27 | REBAR ?= ./rebar 28 | REVISION ?= $(shell git rev-parse --short HEAD) 29 | PROJECT ?= $(shell basename `find src -name "*.app.src"` .app.src) 30 | 31 | .PHONY: compile-no-deps test docs xref dialyzer-run dialyzer-quick dialyzer \ 32 | cleanplt upload-docs 33 | 34 | compile-no-deps: 35 | ${REBAR} compile skip_deps=true 36 | 37 | test: compile 38 | ${REBAR} eunit skip_deps=true 39 | 40 | upload-docs: docs 41 | @if [ -z "${BUCKET}" -o -z "${PROJECT}" -o -z "${REVISION}" ]; then \ 42 | echo "Set BUCKET, PROJECT, and REVISION env vars to upload docs"; \ 43 | exit 1; fi 44 | @cd doc; s3cmd put -P * "s3://${BUCKET}/${PROJECT}/${REVISION}/" > /dev/null 45 | @echo "Docs built at: http://${BUCKET}.s3-website-us-east-1.amazonaws.com/${PROJECT}/${REVISION}" 46 | 47 | docs: 48 | ${REBAR} doc skip_deps=true 49 | 50 | xref: compile 51 | ${REBAR} xref skip_deps=true 52 | 53 | PLT ?= $(HOME)/.combo_dialyzer_plt 54 | LOCAL_PLT = .local_dialyzer_plt 55 | DIALYZER_FLAGS ?= -Wunmatched_returns 56 | 57 | ${PLT}: compile 58 | @if [ -f $(PLT) ]; then \ 59 | dialyzer --check_plt --plt $(PLT) --apps $(DIALYZER_APPS) && \ 60 | dialyzer --add_to_plt --plt $(PLT) --output_plt $(PLT) --apps $(DIALYZER_APPS) ; test $$? -ne 1; \ 61 | else \ 62 | dialyzer --build_plt --output_plt $(PLT) --apps $(DIALYZER_APPS); test $$? -ne 1; \ 63 | fi 64 | 65 | ${LOCAL_PLT}: compile 66 | @if [ -d deps ]; then \ 67 | if [ -f $(LOCAL_PLT) ]; then \ 68 | dialyzer --check_plt --plt $(LOCAL_PLT) deps/*/ebin && \ 69 | dialyzer --add_to_plt --plt $(LOCAL_PLT) --output_plt $(LOCAL_PLT) deps/*/ebin ; test $$? -ne 1; \ 70 | else \ 71 | dialyzer --build_plt --output_plt $(LOCAL_PLT) deps/*/ebin ; test $$? -ne 1; \ 72 | fi \ 73 | fi 74 | 75 | dialyzer-run: 76 | @echo "==> $(shell basename $(shell pwd)) (dialyzer)" 77 | # The bulk of the code below deals with the dialyzer.ignore-warnings file 78 | # which contains strings to ignore if output by dialyzer. 79 | # Typically the strings include line numbers. Using them exactly is hard 80 | # to maintain as the code changes. This approach instead ignores the line 81 | # numbers, but takes into account the number of times a string is listed 82 | # for a given file. So if one string is listed once, for example, and it 83 | # appears twice in the warnings, the user is alerted. It is possible but 84 | # unlikely that this approach could mask a warning if one ignored warning 85 | # is removed and two warnings of the same kind appear in the file, for 86 | # example. But it is a trade-off that seems worth it. 87 | # Details of the cryptic commands: 88 | # - Remove line numbers from dialyzer.ignore-warnings 89 | # - Pre-pend duplicate count to each warning with sort | uniq -c 90 | # - Remove annoying white space around duplicate count 91 | # - Save in dialyer.ignore-warnings.tmp 92 | # - Do the same to dialyzer_warnings 93 | # - Remove matches from dialyzer.ignore-warnings.tmp from output 94 | # - Remove duplicate count 95 | # - Escape regex special chars to use lines as regex patterns 96 | # - Add pattern to match any line number (file.erl:\d+:) 97 | # - Anchor to match the entire line (^entire line$) 98 | # - Save in dialyzer_unhandled_warnings 99 | # - Output matches for those patterns found in the original warnings 100 | @if [ -f $(LOCAL_PLT) ]; then \ 101 | PLTS="$(PLT) $(LOCAL_PLT)"; \ 102 | else \ 103 | PLTS=$(PLT); \ 104 | fi; \ 105 | if [ -f dialyzer.ignore-warnings ]; then \ 106 | if [ $$(grep -cvE '[^[:space:]]' dialyzer.ignore-warnings) -ne 0 ]; then \ 107 | echo "ERROR: dialyzer.ignore-warnings contains a blank/empty line, this will match all messages!"; \ 108 | exit 1; \ 109 | fi; \ 110 | dialyzer $(DIALYZER_FLAGS) --plts $${PLTS} -c ebin > dialyzer_warnings ; \ 111 | cat dialyzer.ignore-warnings \ 112 | | sed -E 's/^([^:]+:)[^:]+:/\1/' \ 113 | | sort \ 114 | | uniq -c \ 115 | | sed -E '/.*\.erl: /!s/^[[:space:]]*[0-9]+[[:space:]]*//' \ 116 | > dialyzer.ignore-warnings.tmp ; \ 117 | egrep -v "^[[:space:]]*(done|Checking|Proceeding|Compiling)" dialyzer_warnings \ 118 | | sed -E 's/^([^:]+:)[^:]+:/\1/' \ 119 | | sort \ 120 | | uniq -c \ 121 | | sed -E '/.*\.erl: /!s/^[[:space:]]*[0-9]+[[:space:]]*//' \ 122 | | grep -F -f dialyzer.ignore-warnings.tmp -v \ 123 | | sed -E 's/^[[:space:]]*[0-9]+[[:space:]]*//' \ 124 | | sed -E 's/([]\^:+?|()*.$${}\[])/\\\1/g' \ 125 | | sed -E 's/(\\\.erl\\\:)/\1\\d+:/g' \ 126 | | sed -E 's/^(.*)$$/^\1$$/g' \ 127 | > dialyzer_unhandled_warnings ; \ 128 | rm dialyzer.ignore-warnings.tmp; \ 129 | if [ $$(cat dialyzer_unhandled_warnings | wc -l) -gt 0 ]; then \ 130 | egrep -f dialyzer_unhandled_warnings dialyzer_warnings ; \ 131 | found_warnings=1; \ 132 | fi; \ 133 | [ "$$found_warnings" != 1 ] ; \ 134 | else \ 135 | dialyzer $(DIALYZER_FLAGS) --plts $${PLTS} -c ebin; \ 136 | fi 137 | 138 | dialyzer-quick: compile-no-deps dialyzer-run 139 | 140 | dialyzer: ${PLT} ${LOCAL_PLT} dialyzer-run 141 | 142 | cleanplt: 143 | @echo 144 | @echo "Are you sure? It takes several minutes to re-build." 145 | @echo Deleting $(PLT) and $(LOCAL_PLT) in 5 seconds. 146 | @echo 147 | sleep 5 148 | rm $(PLT) 149 | rm $(LOCAL_PLT) 150 | --------------------------------------------------------------------------------