]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
1 | // Copyright 2014 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
11 | //! Utilities for program-wide and customizable logging | |
12 | //! | |
13 | //! # Examples | |
14 | //! | |
15 | //! ``` | |
16 | //! #[macro_use] extern crate log; | |
17 | //! | |
18 | //! fn main() { | |
19 | //! debug!("this is a debug {:?}", "message"); | |
20 | //! error!("this is printed by default"); | |
21 | //! | |
22 | //! if log_enabled!(log::INFO) { | |
85aaf69f | 23 | //! let x = 3 * 4; // expensive computation |
1a4d82fc JJ |
24 | //! info!("the answer was: {:?}", x); |
25 | //! } | |
26 | //! } | |
27 | //! ``` | |
28 | //! | |
29 | //! Assumes the binary is `main`: | |
30 | //! | |
31 | //! ```{.bash} | |
32 | //! $ RUST_LOG=error ./main | |
33 | //! ERROR:main: this is printed by default | |
34 | //! ``` | |
35 | //! | |
36 | //! ```{.bash} | |
37 | //! $ RUST_LOG=info ./main | |
38 | //! ERROR:main: this is printed by default | |
39 | //! INFO:main: the answer was: 12 | |
40 | //! ``` | |
41 | //! | |
42 | //! ```{.bash} | |
43 | //! $ RUST_LOG=debug ./main | |
44 | //! DEBUG:main: this is a debug message | |
45 | //! ERROR:main: this is printed by default | |
46 | //! INFO:main: the answer was: 12 | |
47 | //! ``` | |
48 | //! | |
49 | //! You can also set the log level on a per module basis: | |
50 | //! | |
51 | //! ```{.bash} | |
52 | //! $ RUST_LOG=main=info ./main | |
53 | //! ERROR:main: this is printed by default | |
54 | //! INFO:main: the answer was: 12 | |
55 | //! ``` | |
56 | //! | |
57 | //! And enable all logging: | |
58 | //! | |
59 | //! ```{.bash} | |
60 | //! $ RUST_LOG=main ./main | |
61 | //! DEBUG:main: this is a debug message | |
62 | //! ERROR:main: this is printed by default | |
63 | //! INFO:main: the answer was: 12 | |
64 | //! ``` | |
65 | //! | |
66 | //! # Logging Macros | |
67 | //! | |
68 | //! There are five macros that the logging subsystem uses: | |
69 | //! | |
70 | //! * `log!(level, ...)` - the generic logging macro, takes a level as a u32 and any | |
71 | //! related `format!` arguments | |
72 | //! * `debug!(...)` - a macro hard-wired to the log level of `DEBUG` | |
73 | //! * `info!(...)` - a macro hard-wired to the log level of `INFO` | |
74 | //! * `warn!(...)` - a macro hard-wired to the log level of `WARN` | |
75 | //! * `error!(...)` - a macro hard-wired to the log level of `ERROR` | |
76 | //! | |
77 | //! All of these macros use the same style of syntax as the `format!` syntax | |
78 | //! extension. Details about the syntax can be found in the documentation of | |
79 | //! `std::fmt` along with the Rust tutorial/manual. | |
80 | //! | |
81 | //! If you want to check at runtime if a given logging level is enabled (e.g. if the | |
82 | //! information you would want to log is expensive to produce), you can use the | |
83 | //! following macro: | |
84 | //! | |
85 | //! * `log_enabled!(level)` - returns true if logging of the given level is enabled | |
86 | //! | |
87 | //! # Enabling logging | |
88 | //! | |
89 | //! Log levels are controlled on a per-module basis, and by default all logging is | |
90 | //! disabled except for `error!` (a log level of 1). Logging is controlled via the | |
91 | //! `RUST_LOG` environment variable. The value of this environment variable is a | |
92 | //! comma-separated list of logging directives. A logging directive is of the form: | |
93 | //! | |
94 | //! ```text | |
95 | //! path::to::module=log_level | |
96 | //! ``` | |
97 | //! | |
98 | //! The path to the module is rooted in the name of the crate it was compiled for, | |
99 | //! so if your program is contained in a file `hello.rs`, for example, to turn on | |
100 | //! logging for this file you would use a value of `RUST_LOG=hello`. | |
101 | //! Furthermore, this path is a prefix-search, so all modules nested in the | |
102 | //! specified module will also have logging enabled. | |
103 | //! | |
104 | //! The actual `log_level` is optional to specify. If omitted, all logging will be | |
105 | //! enabled. If specified, the it must be either a numeric in the range of 1-255, or | |
106 | //! it must be one of the strings `debug`, `error`, `info`, or `warn`. If a numeric | |
107 | //! is specified, then all logging less than or equal to that numeral is enabled. | |
108 | //! For example, if logging level 3 is active, error, warn, and info logs will be | |
109 | //! printed, but debug will be omitted. | |
110 | //! | |
111 | //! As the log level for a module is optional, the module to enable logging for is | |
112 | //! also optional. If only a `log_level` is provided, then the global log level for | |
113 | //! all modules is set to this value. | |
114 | //! | |
115 | //! Some examples of valid values of `RUST_LOG` are: | |
116 | //! | |
117 | //! * `hello` turns on all logging for the 'hello' module | |
118 | //! * `info` turns on all info logging | |
119 | //! * `hello=debug` turns on debug logging for 'hello' | |
120 | //! * `hello=3` turns on info logging for 'hello' | |
121 | //! * `hello,std::option` turns on hello, and std's option logging | |
122 | //! * `error,hello=warn` turn on global error logging and also warn for hello | |
123 | //! | |
124 | //! # Filtering results | |
125 | //! | |
85aaf69f SL |
126 | //! A RUST_LOG directive may include a string filter. The syntax is to append |
127 | //! `/` followed by a string. Each message is checked against the string and is | |
128 | //! only logged if it contains the string. Note that the matching is done after | |
129 | //! formatting the log string but before adding any logging meta-data. There is | |
130 | //! a single filter for all modules. | |
1a4d82fc JJ |
131 | //! |
132 | //! Some examples: | |
133 | //! | |
134 | //! * `hello/foo` turns on all logging for the 'hello' module where the log message | |
135 | //! includes 'foo'. | |
136 | //! * `info/f.o` turns on all info logging where the log message includes 'foo', | |
137 | //! 'f1o', 'fao', etc. | |
138 | //! * `hello=debug/foo*foo` turns on debug logging for 'hello' where the log | |
139 | //! message includes 'foofoo' or 'fofoo' or 'fooooooofoo', etc. | |
140 | //! * `error,hello=warn/[0-9] scopes` turn on global error logging and also warn for | |
141 | //! hello. In both cases the log message must include a single digit number | |
142 | //! followed by 'scopes' | |
143 | //! | |
144 | //! # Performance and Side Effects | |
145 | //! | |
146 | //! Each of these macros will expand to code similar to: | |
147 | //! | |
148 | //! ```rust,ignore | |
149 | //! if log_level <= my_module_log_level() { | |
150 | //! ::log::log(log_level, format!(...)); | |
151 | //! } | |
152 | //! ``` | |
153 | //! | |
154 | //! What this means is that each of these macros are very cheap at runtime if | |
155 | //! they're turned off (just a load and an integer comparison). This also means that | |
156 | //! if logging is disabled, none of the components of the log will be executed. | |
157 | ||
c34b1796 AL |
158 | // Do not remove on snapshot creation. Needed for bootstrap. (Issue #22364) |
159 | #![cfg_attr(stage0, feature(custom_attribute))] | |
1a4d82fc | 160 | #![crate_name = "log"] |
85aaf69f | 161 | #![unstable(feature = "rustc_private", |
e9174d1e SL |
162 | reason = "use the crates.io `log` library instead", |
163 | issue = "27812")] | |
1a4d82fc JJ |
164 | #![staged_api] |
165 | #![crate_type = "rlib"] | |
166 | #![crate_type = "dylib"] | |
e9174d1e | 167 | #![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", |
62682a34 | 168 | html_favicon_url = "https://doc.rust-lang.org/favicon.ico", |
e9174d1e SL |
169 | html_root_url = "https://doc.rust-lang.org/nightly/", |
170 | html_playground_url = "https://play.rust-lang.org/")] | |
1a4d82fc JJ |
171 | #![deny(missing_docs)] |
172 | ||
85aaf69f | 173 | #![feature(box_syntax)] |
62682a34 SL |
174 | #![feature(const_fn)] |
175 | #![feature(iter_cmp)] | |
62682a34 SL |
176 | #![feature(staged_api)] |
177 | #![feature(static_mutex)] | |
1a4d82fc JJ |
178 | |
179 | use std::cell::RefCell; | |
180 | use std::fmt; | |
c34b1796 AL |
181 | use std::io::{self, Stderr}; |
182 | use std::io::prelude::*; | |
1a4d82fc | 183 | use std::mem; |
85aaf69f | 184 | use std::env; |
e9174d1e | 185 | use std::ptr; |
1a4d82fc | 186 | use std::slice; |
62682a34 | 187 | use std::sync::{Once, StaticMutex}; |
1a4d82fc | 188 | |
1a4d82fc JJ |
189 | use directive::LOG_LEVEL_NAMES; |
190 | ||
191 | #[macro_use] | |
192 | pub mod macros; | |
193 | ||
194 | mod directive; | |
195 | ||
196 | /// Maximum logging level of a module that can be specified. Common logging | |
197 | /// levels are found in the DEBUG/INFO/WARN/ERROR constants. | |
198 | pub const MAX_LOG_LEVEL: u32 = 255; | |
199 | ||
200 | /// The default logging level of a crate if no other is specified. | |
201 | const DEFAULT_LOG_LEVEL: u32 = 1; | |
202 | ||
62682a34 | 203 | static LOCK: StaticMutex = StaticMutex::new(); |
c34b1796 | 204 | |
1a4d82fc JJ |
205 | /// An unsafe constant that is the maximum logging level of any module |
206 | /// specified. This is the first line of defense to determining whether a | |
207 | /// logging statement should be run. | |
208 | static mut LOG_LEVEL: u32 = MAX_LOG_LEVEL; | |
209 | ||
e9174d1e | 210 | static mut DIRECTIVES: *mut Vec<directive::LogDirective> = ptr::null_mut(); |
1a4d82fc | 211 | |
85aaf69f | 212 | /// Optional filter. |
e9174d1e | 213 | static mut FILTER: *mut String = ptr::null_mut(); |
1a4d82fc JJ |
214 | |
215 | /// Debug log level | |
216 | pub const DEBUG: u32 = 4; | |
217 | /// Info log level | |
218 | pub const INFO: u32 = 3; | |
219 | /// Warn log level | |
220 | pub const WARN: u32 = 2; | |
221 | /// Error log level | |
222 | pub const ERROR: u32 = 1; | |
223 | ||
224 | thread_local! { | |
225 | static LOCAL_LOGGER: RefCell<Option<Box<Logger + Send>>> = { | |
226 | RefCell::new(None) | |
227 | } | |
228 | } | |
229 | ||
bd371182 | 230 | /// A trait used to represent an interface to a thread-local logger. Each thread |
1a4d82fc JJ |
231 | /// can have its own custom logger which can respond to logging messages |
232 | /// however it likes. | |
233 | pub trait Logger { | |
234 | /// Logs a single message described by the `record`. | |
235 | fn log(&mut self, record: &LogRecord); | |
236 | } | |
237 | ||
b039eaaf SL |
238 | struct DefaultLogger { |
239 | handle: Stderr, | |
240 | } | |
1a4d82fc JJ |
241 | |
242 | /// Wraps the log level with fmt implementations. | |
c34b1796 | 243 | #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] |
1a4d82fc JJ |
244 | pub struct LogLevel(pub u32); |
245 | ||
85aaf69f | 246 | impl fmt::Display for LogLevel { |
1a4d82fc JJ |
247 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
248 | let LogLevel(level) = *self; | |
c34b1796 | 249 | match LOG_LEVEL_NAMES.get(level as usize - 1) { |
85aaf69f | 250 | Some(ref name) => fmt::Display::fmt(name, fmt), |
b039eaaf | 251 | None => fmt::Display::fmt(&level, fmt), |
1a4d82fc JJ |
252 | } |
253 | } | |
254 | } | |
255 | ||
256 | impl Logger for DefaultLogger { | |
257 | fn log(&mut self, record: &LogRecord) { | |
258 | match writeln!(&mut self.handle, | |
259 | "{}:{}: {}", | |
260 | record.level, | |
261 | record.module_path, | |
262 | record.args) { | |
263 | Err(e) => panic!("failed to log: {:?}", e), | |
264 | Ok(()) => {} | |
265 | } | |
266 | } | |
267 | } | |
268 | ||
269 | impl Drop for DefaultLogger { | |
270 | fn drop(&mut self) { | |
271 | // FIXME(#12628): is panicking the right thing to do? | |
272 | match self.handle.flush() { | |
273 | Err(e) => panic!("failed to flush a logger: {:?}", e), | |
274 | Ok(()) => {} | |
275 | } | |
276 | } | |
277 | } | |
278 | ||
279 | /// This function is called directly by the compiler when using the logging | |
280 | /// macros. This function does not take into account whether the log level | |
281 | /// specified is active or not, it will always log something if this method is | |
282 | /// called. | |
283 | /// | |
284 | /// It is not recommended to call this function directly, rather it should be | |
285 | /// invoked through the logging family of macros. | |
286 | #[doc(hidden)] | |
287 | pub fn log(level: u32, loc: &'static LogLocation, args: fmt::Arguments) { | |
288 | // Test the literal string from args against the current filter, if there | |
289 | // is one. | |
c34b1796 AL |
290 | unsafe { |
291 | let _g = LOCK.lock(); | |
292 | match FILTER as usize { | |
293 | 0 => {} | |
c34b1796 AL |
294 | n => { |
295 | let filter = mem::transmute::<_, &String>(n); | |
e9174d1e | 296 | if !args.to_string().contains(filter) { |
c34b1796 AL |
297 | return |
298 | } | |
299 | } | |
300 | } | |
1a4d82fc JJ |
301 | } |
302 | ||
303 | // Completely remove the local logger from TLS in case anyone attempts to | |
304 | // frob the slot while we're doing the logging. This will destroy any logger | |
305 | // set during logging. | |
b039eaaf SL |
306 | let mut logger: Box<Logger + Send> = LOCAL_LOGGER.with(|s| s.borrow_mut().take()) |
307 | .unwrap_or_else(|| { | |
308 | box DefaultLogger { handle: io::stderr() } | |
309 | }); | |
1a4d82fc JJ |
310 | logger.log(&LogRecord { |
311 | level: LogLevel(level), | |
312 | args: args, | |
313 | file: loc.file, | |
314 | module_path: loc.module_path, | |
315 | line: loc.line, | |
316 | }); | |
317 | set_logger(logger); | |
318 | } | |
319 | ||
320 | /// Getter for the global log level. This is a function so that it can be called | |
321 | /// safely | |
322 | #[doc(hidden)] | |
323 | #[inline(always)] | |
b039eaaf SL |
324 | pub fn log_level() -> u32 { |
325 | unsafe { LOG_LEVEL } | |
326 | } | |
1a4d82fc | 327 | |
bd371182 | 328 | /// Replaces the thread-local logger with the specified logger, returning the old |
1a4d82fc JJ |
329 | /// logger. |
330 | pub fn set_logger(logger: Box<Logger + Send>) -> Option<Box<Logger + Send>> { | |
b039eaaf | 331 | LOCAL_LOGGER.with(|slot| mem::replace(&mut *slot.borrow_mut(), Some(logger))) |
1a4d82fc JJ |
332 | } |
333 | ||
334 | /// A LogRecord is created by the logging macros, and passed as the only | |
335 | /// argument to Loggers. | |
85aaf69f | 336 | #[derive(Debug)] |
1a4d82fc | 337 | pub struct LogRecord<'a> { |
1a4d82fc JJ |
338 | /// The module path of where the LogRecord originated. |
339 | pub module_path: &'a str, | |
340 | ||
341 | /// The LogLevel of this record. | |
342 | pub level: LogLevel, | |
343 | ||
344 | /// The arguments from the log line. | |
345 | pub args: fmt::Arguments<'a>, | |
346 | ||
347 | /// The file of where the LogRecord originated. | |
348 | pub file: &'a str, | |
349 | ||
350 | /// The line number of where the LogRecord originated. | |
c34b1796 | 351 | pub line: u32, |
1a4d82fc JJ |
352 | } |
353 | ||
354 | #[doc(hidden)] | |
c34b1796 | 355 | #[derive(Copy, Clone)] |
1a4d82fc JJ |
356 | pub struct LogLocation { |
357 | pub module_path: &'static str, | |
358 | pub file: &'static str, | |
c34b1796 | 359 | pub line: u32, |
1a4d82fc JJ |
360 | } |
361 | ||
362 | /// Tests whether a given module's name is enabled for a particular level of | |
363 | /// logging. This is the second layer of defense about determining whether a | |
364 | /// module's log statement should be emitted or not. | |
365 | #[doc(hidden)] | |
366 | pub fn mod_enabled(level: u32, module: &str) -> bool { | |
62682a34 | 367 | static INIT: Once = Once::new(); |
1a4d82fc JJ |
368 | INIT.call_once(init); |
369 | ||
370 | // It's possible for many threads are in this function, only one of them | |
371 | // will perform the global initialization, but all of them will need to check | |
372 | // again to whether they should really be here or not. Hence, despite this | |
373 | // check being expanded manually in the logging macro, this function checks | |
374 | // the log level again. | |
b039eaaf SL |
375 | if level > unsafe { LOG_LEVEL } { |
376 | return false | |
377 | } | |
1a4d82fc JJ |
378 | |
379 | // This assertion should never get tripped unless we're in an at_exit | |
380 | // handler after logging has been torn down and a logging attempt was made. | |
1a4d82fc | 381 | |
c34b1796 AL |
382 | let _g = LOCK.lock(); |
383 | unsafe { | |
384 | assert!(DIRECTIVES as usize != 0); | |
c34b1796 AL |
385 | enabled(level, module, (*DIRECTIVES).iter()) |
386 | } | |
1a4d82fc JJ |
387 | } |
388 | ||
b039eaaf | 389 | fn enabled(level: u32, module: &str, iter: slice::Iter<directive::LogDirective>) -> bool { |
1a4d82fc JJ |
390 | // Search for the longest match, the vector is assumed to be pre-sorted. |
391 | for directive in iter.rev() { | |
392 | match directive.name { | |
b039eaaf | 393 | Some(ref name) if !module.starts_with(&name[..]) => {} |
1a4d82fc JJ |
394 | Some(..) | None => { |
395 | return level <= directive.level | |
396 | } | |
397 | } | |
398 | } | |
399 | level <= DEFAULT_LOG_LEVEL | |
400 | } | |
401 | ||
402 | /// Initialize logging for the current process. | |
403 | /// | |
404 | /// This is not threadsafe at all, so initialization is performed through a | |
405 | /// `Once` primitive (and this function is called from that primitive). | |
406 | fn init() { | |
85aaf69f SL |
407 | let (mut directives, filter) = match env::var("RUST_LOG") { |
408 | Ok(spec) => directive::parse_logging_spec(&spec[..]), | |
409 | Err(..) => (Vec::new(), None), | |
1a4d82fc JJ |
410 | }; |
411 | ||
412 | // Sort the provided directives by length of their name, this allows a | |
413 | // little more efficient lookup at runtime. | |
414 | directives.sort_by(|a, b| { | |
415 | let alen = a.name.as_ref().map(|a| a.len()).unwrap_or(0); | |
416 | let blen = b.name.as_ref().map(|b| b.len()).unwrap_or(0); | |
417 | alen.cmp(&blen) | |
418 | }); | |
419 | ||
420 | let max_level = { | |
421 | let max = directives.iter().max_by(|d| d.level); | |
422 | max.map(|d| d.level).unwrap_or(DEFAULT_LOG_LEVEL) | |
423 | }; | |
424 | ||
425 | unsafe { | |
426 | LOG_LEVEL = max_level; | |
427 | ||
428 | assert!(FILTER.is_null()); | |
429 | match filter { | |
62682a34 | 430 | Some(f) => FILTER = Box::into_raw(box f), |
1a4d82fc JJ |
431 | None => {} |
432 | } | |
433 | ||
434 | assert!(DIRECTIVES.is_null()); | |
62682a34 | 435 | DIRECTIVES = Box::into_raw(box directives); |
1a4d82fc JJ |
436 | } |
437 | } | |
438 | ||
439 | #[cfg(test)] | |
440 | mod tests { | |
441 | use super::enabled; | |
442 | use directive::LogDirective; | |
443 | ||
444 | #[test] | |
445 | fn match_full_path() { | |
b039eaaf SL |
446 | let dirs = [LogDirective { |
447 | name: Some("crate2".to_string()), | |
448 | level: 3, | |
449 | }, | |
450 | LogDirective { | |
451 | name: Some("crate1::mod1".to_string()), | |
452 | level: 2, | |
453 | }]; | |
1a4d82fc JJ |
454 | assert!(enabled(2, "crate1::mod1", dirs.iter())); |
455 | assert!(!enabled(3, "crate1::mod1", dirs.iter())); | |
456 | assert!(enabled(3, "crate2", dirs.iter())); | |
457 | assert!(!enabled(4, "crate2", dirs.iter())); | |
458 | } | |
459 | ||
460 | #[test] | |
461 | fn no_match() { | |
b039eaaf SL |
462 | let dirs = [LogDirective { |
463 | name: Some("crate2".to_string()), | |
464 | level: 3, | |
465 | }, | |
466 | LogDirective { | |
467 | name: Some("crate1::mod1".to_string()), | |
468 | level: 2, | |
469 | }]; | |
1a4d82fc JJ |
470 | assert!(!enabled(2, "crate3", dirs.iter())); |
471 | } | |
472 | ||
473 | #[test] | |
474 | fn match_beginning() { | |
b039eaaf SL |
475 | let dirs = [LogDirective { |
476 | name: Some("crate2".to_string()), | |
477 | level: 3, | |
478 | }, | |
479 | LogDirective { | |
480 | name: Some("crate1::mod1".to_string()), | |
481 | level: 2, | |
482 | }]; | |
1a4d82fc JJ |
483 | assert!(enabled(3, "crate2::mod1", dirs.iter())); |
484 | } | |
485 | ||
486 | #[test] | |
487 | fn match_beginning_longest_match() { | |
b039eaaf SL |
488 | let dirs = [LogDirective { |
489 | name: Some("crate2".to_string()), | |
490 | level: 3, | |
491 | }, | |
492 | LogDirective { | |
493 | name: Some("crate2::mod".to_string()), | |
494 | level: 4, | |
495 | }, | |
496 | LogDirective { | |
497 | name: Some("crate1::mod1".to_string()), | |
498 | level: 2, | |
499 | }]; | |
1a4d82fc JJ |
500 | assert!(enabled(4, "crate2::mod1", dirs.iter())); |
501 | assert!(!enabled(4, "crate2", dirs.iter())); | |
502 | } | |
503 | ||
504 | #[test] | |
505 | fn match_default() { | |
b039eaaf SL |
506 | let dirs = [LogDirective { |
507 | name: None, | |
508 | level: 3, | |
509 | }, | |
510 | LogDirective { | |
511 | name: Some("crate1::mod1".to_string()), | |
512 | level: 2, | |
513 | }]; | |
1a4d82fc JJ |
514 | assert!(enabled(2, "crate1::mod1", dirs.iter())); |
515 | assert!(enabled(3, "crate2::mod2", dirs.iter())); | |
516 | } | |
517 | ||
518 | #[test] | |
519 | fn zero_level() { | |
b039eaaf SL |
520 | let dirs = [LogDirective { |
521 | name: None, | |
522 | level: 3, | |
523 | }, | |
524 | LogDirective { | |
525 | name: Some("crate1::mod1".to_string()), | |
526 | level: 0, | |
527 | }]; | |
1a4d82fc JJ |
528 | assert!(!enabled(1, "crate1::mod1", dirs.iter())); |
529 | assert!(enabled(3, "crate2::mod2", dirs.iter())); | |
530 | } | |
531 | } |