testbeats 2.0.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- > !IMPORTANT - This npm package has been renamed from [test-results-reporter](https://www.npmjs.com/package/test-results-reporter) to [testbeats](https://www.npmjs.com/package/testbeats). test-results-reporter will soon be phased out, and users are encouraged to transition to testbeats.
1
+ > This npm package has been renamed from [test-results-reporter](https://www.npmjs.com/package/test-results-reporter) to [testbeats](https://www.npmjs.com/package/testbeats). test-results-reporter will soon be phased out, and users are encouraged to transition to testbeats.
2
2
 
3
3
  <span align="center">
4
4
 
@@ -19,35 +19,23 @@
19
19
 
20
20
  <hr>
21
21
 
22
- ### Targets
23
-
24
- <img height="48" style="margin: 6px;" src="./assets/slack.png" alt="slack" /> <img height="48" style="margin: 6px;" src="./assets/teams.png" alt="teams" /> <img height="48" style="margin: 6px;" src="./assets/chat.png" alt="chat" />
25
-
26
- ### Extensions
27
-
28
- <img height="48" style="margin: 6px;" src="./assets/reportportal.jpeg" alt="reportportal" /> <img height="48" style="margin: 6px;" src="./assets/quickchart.png" alt="quickchart" /> <img height="48" style="margin: 6px;" src="./assets/hyperlink.png" alt="hyperlink" /> <img height="48" style="margin: 6px;" src="./assets/mentions.png" alt="mentions" />
29
-
30
- ### Test Results
31
-
32
- <img height="48" style="margin: 6px;" src="./assets/testng.png" alt="testng" /> <img height="48" style="margin: 6px;" src="./assets/junit.png" alt="junit" /> <img height="48" style="margin: 6px;" src="./assets/cucumber.png" alt="cucumber" /> <img height="48" style="margin: 6px;" src="./assets/mocha.png" alt="mocha" /> <img height="48" style="margin: 6px;" src="./assets/xunit.png" alt="xunit" /> <img height="48" style="margin: 6px;" src="./assets/jmeter.png" alt="jmeter" />
22
+ </span>
33
23
 
34
- <hr>
24
+ ### Get Started
35
25
 
36
- ## Sample Reports
26
+ TestBeats is a tool designed to streamline the process of publishing test results from various automation testing frameworks to communication platforms like **slack**, **teams** and more for easy access and collaboration. It unifies your test reporting to build quality insights and make faster decisions.
37
27
 
38
- <br>
28
+ Read more about the project at [https://testbeats.com](https://testbeats.com)
39
29
 
40
- ![teams-summary-report](https://github.com/test-results-reporter/testbeats/raw/main/assets/teams-qc.png)
30
+ ### Sample Reports
41
31
 
42
- ![slack-summary-report](https://github.com/test-results-reporter/testbeats/raw/main/assets/slack-report-portal-analysis.png)
32
+ #### Alerts in Slack
43
33
 
44
- <br />
34
+ ![testbeats-failure-summary](./assets/testbeats-slack-failure-summary.png)
45
35
 
46
- <hr >
36
+ #### Results in Portal
47
37
 
48
- # [Documentation](https://test-results-reporter.github.io/)
49
-
50
- </span>
38
+ ![testbeats-failure-summary](./assets/testbeats-failure-summary.png)
51
39
 
52
40
  <br />
53
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testbeats",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Publish test results to Microsoft Teams, Google Chat, Slack and InfluxDB",
5
5
  "main": "src/index.js",
6
6
  "types": "./src/index.d.ts",
@@ -0,0 +1,40 @@
1
+ const request = require('phin-retry');
2
+
3
+ class BeatsApi {
4
+
5
+ /**
6
+ * @param {import('../index').PublishReport} config
7
+ */
8
+ constructor(config) {
9
+ this.config = config;
10
+ }
11
+
12
+ postTestRun(payload) {
13
+ return request.post({
14
+ url: `${this.getBaseUrl()}/api/core/v1/test-runs`,
15
+ headers: {
16
+ 'x-api-key': this.config.api_key
17
+ },
18
+ body: payload
19
+ });
20
+ }
21
+
22
+ /**
23
+ * @param {string} run_id
24
+ * @returns
25
+ */
26
+ getTestRun(run_id) {
27
+ return request.get({
28
+ url: `${this.getBaseUrl()}/api/core/v1/test-runs/key?id=${run_id}`,
29
+ headers: {
30
+ 'x-api-key': this.config.api_key
31
+ }
32
+ });
33
+ }
34
+
35
+ getBaseUrl() {
36
+ return process.env.TEST_BEATS_URL || "https://app.testbeats.com";
37
+ }
38
+ }
39
+
40
+ module.exports = { BeatsApi }
@@ -0,0 +1,151 @@
1
+ const { getCIInformation } = require('../helpers/ci');
2
+ const logger = require('../utils/logger');
3
+ const { BeatsApi } = require('./beats.api');
4
+ const { HOOK } = require('../helpers/constants');
5
+
6
+ class Beats {
7
+
8
+ /**
9
+ * @param {import('../index').PublishReport} config
10
+ * @param {TestResult} result
11
+ */
12
+ constructor(config, result) {
13
+ this.config = config;
14
+ this.result = result;
15
+ this.api = new BeatsApi(config);
16
+ this.test_run_id = '';
17
+ }
18
+
19
+ async publish() {
20
+ this.#setCIInfo();
21
+ this.#setProjectName();
22
+ this.#setRunName();
23
+ this.#setApiKey();
24
+ await this.#publishTestResults();
25
+ this.#updateTitleLink();
26
+ await this.#attachFailureSummary();
27
+ }
28
+
29
+ #setCIInfo() {
30
+ this.ci = getCIInformation();
31
+ }
32
+
33
+ #setProjectName() {
34
+ this.config.project = this.config.project || process.env.TEST_BEATS_PROJECT || (this.ci && this.ci.repository_name) || 'demo-project';
35
+ }
36
+
37
+ #setApiKey() {
38
+ this.config.api_key = this.config.api_key || process.env.TEST_BEATS_API_KEY;
39
+ }
40
+
41
+ #setRunName() {
42
+ this.config.run = this.config.run || process.env.TEST_BEATS_RUN || (this.ci && this.ci.build_name) || 'demo-run';
43
+ }
44
+
45
+ async #publishTestResults() {
46
+ if (!this.config.api_key) {
47
+ logger.warn('😿 No API key provided, skipping publishing results to TestBeats Portal');
48
+ return;
49
+ }
50
+ logger.info("🚀 Publishing results to TestBeats Portal...");
51
+ try {
52
+ const payload = this.#getPayload();
53
+ const response = await this.api.postTestRun(payload);
54
+ this.test_run_id = response.id;
55
+ } catch (error) {
56
+ logger.error(`❌ Unable to publish results to TestBeats Portal: ${error.message}`, error);
57
+ }
58
+ }
59
+
60
+ #getPayload() {
61
+ const payload = {
62
+ project: this.config.project,
63
+ run: this.config.run,
64
+ ...this.result
65
+ }
66
+ if (this.ci) {
67
+ payload.ci_details = [this.ci];
68
+ }
69
+ return payload;
70
+ }
71
+
72
+ #updateTitleLink() {
73
+ if (!this.test_run_id) {
74
+ return;
75
+ }
76
+ if (!this.config.targets) {
77
+ return;
78
+ }
79
+ const link = `${this.api.getBaseUrl()}/reports/${this.test_run_id}`;
80
+ for (const target of this.config.targets) {
81
+ target.inputs.title_link = link;
82
+ }
83
+ }
84
+
85
+ async #attachFailureSummary() {
86
+ if (!this.test_run_id) {
87
+ return;
88
+ }
89
+ if (!this.config.targets) {
90
+ return;
91
+ }
92
+ if (this.result.status !== 'FAIL') {
93
+ return;
94
+ }
95
+ if (this.config.show_failure_summary === false) {
96
+ return;
97
+ }
98
+ const text = await this.#getFailureSummary();
99
+ if (!text) {
100
+ return;
101
+ }
102
+ const extension = this.#getAIFailureSummaryExtension(text);
103
+ for (const target of this.config.targets) {
104
+ target.extensions = target.extensions || [];
105
+ target.extensions.push(extension);
106
+ }
107
+ }
108
+
109
+ async #getFailureSummary() {
110
+ logger.info('✨ Fetching AI Failure Summary...');
111
+ let retry = 3;
112
+ while (retry >= 0) {
113
+ retry = retry - 1;
114
+ await new Promise(resolve => setTimeout(resolve, this.#getDelay()));
115
+ const test_run = await this.api.getTestRun(this.test_run_id);
116
+ const status = test_run && test_run.failure_summary_status;
117
+ switch (status) {
118
+ case 'COMPLETED':
119
+ return test_run.execution_metrics[0].failure_summary;
120
+ case 'FAILED':
121
+ logger.error(`❌ Failed to generate AI Failure Summary`);
122
+ return;
123
+ case 'SKIPPED':
124
+ logger.warn(`❗ Skipped generating AI Failure Summary`);
125
+ return;
126
+ }
127
+ logger.info(`🔄 AI Failure Summary not generated, retrying...`);
128
+ }
129
+ logger.warn(`🙈 AI Failure Summary not generated in given time`);
130
+ }
131
+
132
+ #getDelay() {
133
+ if (process.env.TEST_BEATS_DELAY) {
134
+ return parseInt(process.env.TEST_BEATS_DELAY);
135
+ }
136
+ return 3000;
137
+ }
138
+
139
+ #getAIFailureSummaryExtension(text) {
140
+ return {
141
+ name: 'ai-failure-summary',
142
+ hook: HOOK.AFTER_SUMMARY,
143
+ inputs: {
144
+ failure_summary: text
145
+ }
146
+ };
147
+ }
148
+
149
+ }
150
+
151
+ module.exports = { Beats }
@@ -1,174 +1,13 @@
1
- const request = require('phin-retry');
2
1
  const TestResult = require('test-results-parser/src/models/TestResult');
3
- const { getCIInformation } = require('../helpers/ci');
4
- const { HOOK } = require('../helpers/constants');
5
-
6
- function get_base_url() {
7
- return process.env.TEST_BEATS_URL || "https://app.testbeats.com";
8
- }
2
+ const { Beats } = require('./beats');
9
3
 
10
4
  /**
11
5
  * @param {import('../index').PublishReport} config
12
6
  * @param {TestResult} result
13
7
  */
14
8
  async function run(config, result) {
15
- init(config);
16
- if (isValid(config)) {
17
- const run_id = await publishTestResults(config, result);
18
- if (run_id) {
19
- attachTestBeatsReportHyperLink(config, run_id);
20
- await attachTestBeatsFailureSummary(config, result, run_id);
21
- }
22
- } else {
23
- console.warn('Missing testbeats config parameters');
24
- }
25
- }
26
-
27
- /**
28
- *
29
- * @param {import('../index').PublishReport} config
30
- */
31
- function init(config) {
32
- config.project = config.project || process.env.TEST_BEATS_PROJECT;
33
- config.run = config.run || process.env.TEST_BEATS_RUN;
34
- config.api_key = config.api_key || process.env.TEST_BEATS_API_KEY;
35
- }
36
-
37
- /**
38
- *
39
- * @param {import('../index').PublishReport} config
40
- */
41
- function isValid(config) {
42
- return config.project && config.run && config.api_key
43
- }
44
-
45
- /**
46
- * @param {import('../index').PublishReport} config
47
- * @param {TestResult} result
48
- */
49
- async function publishTestResults(config, result) {
50
- console.log("Publishing results to TestBeats");
51
- try {
52
- const payload = {
53
- project: config.project,
54
- run: config.run,
55
- ...result
56
- }
57
- const ci = getCIInformation();
58
- if (ci) {
59
- payload.ci_details = [ci];
60
- }
61
-
62
- const response = await request.post({
63
- url: `${get_base_url()}/api/core/v1/test-runs`,
64
- headers: {
65
- 'x-api-key': config.api_key
66
- },
67
- body: payload
68
- });
69
- return response.id;
70
- } catch (error) {
71
- console.log("Unable to publish results to TestBeats");
72
- console.log(error);
73
- }
74
- }
75
-
76
- /**
77
- * @param {import('../index').PublishReport} config
78
- * @param {string} run_id
79
- */
80
- function attachTestBeatsReportHyperLink(config, run_id) {
81
- const beats_link = get_test_beats_report_link(run_id);
82
- if (config.targets) {
83
- for (const target of config.targets) {
84
- target.inputs.title_link = beats_link;
85
- }
86
- }
87
- }
88
-
89
- /**
90
- * @param {import('../index').PublishReport} config
91
- * @param {TestResult} result
92
- * @param {string} run_id
93
- */
94
- async function attachTestBeatsFailureSummary(config, result, run_id) {
95
- if (result.status !== 'FAIL') {
96
- return;
97
- }
98
- if (config.show_failure_summary === false) {
99
- return;
100
- }
101
- try {
102
- await processFailureSummary(config, run_id);
103
- } catch (error) {
104
- console.log(error);
105
- console.log("error processing failure summary");
106
- }
107
- }
108
-
109
- async function processFailureSummary(config, run_id) {
110
- let retry = 3;
111
- while (retry > 0) {
112
- const test_run = await getTestRun(config, run_id);
113
- if (test_run && test_run.failure_summary_status) {
114
- if (test_run.failure_summary_status === 'COMPLETED') {
115
- addAIFailureSummaryExtension(config, test_run);
116
- return;
117
- } else if (test_run.failure_summary_status === 'FAILED') {
118
- console.log(`Test run failure summary failed`);
119
- return;
120
- } else if (test_run.failure_summary_status === 'SKIPPED') {
121
- console.log(`Test run failure summary failed`);
122
- return;
123
- }
124
- }
125
- console.log(`Test run failure summary not completed, retrying...`);
126
- await new Promise(resolve => setTimeout(resolve, 3000));
127
- retry = retry - 1;
128
- }
129
- }
130
-
131
- /**
132
- * @param {import('../index').PublishReport} config
133
- * @param {string} run_id
134
- */
135
- function getTestRun(config, run_id) {
136
- return request.get({
137
- url: `${get_base_url()}/api/core/v1/test-runs/key?id=${run_id}`,
138
- headers: {
139
- 'x-api-key': config.api_key
140
- }
141
- });
142
- }
143
-
144
- function getAIFailureSummaryExtension(test_run) {
145
- const execution_metric = test_run.execution_metrics[0];
146
- return {
147
- name: 'ai-failure-summary',
148
- hook: HOOK.AFTER_SUMMARY,
149
- inputs: {
150
- failure_summary: execution_metric.failure_summary
151
- }
152
- };
153
- }
154
-
155
- function addAIFailureSummaryExtension(config, test_run) {
156
- const extension = getAIFailureSummaryExtension(test_run);
157
- if (config.targets) {
158
- for (const target of config.targets) {
159
- target.extensions = target.extensions || [];
160
- target.extensions.push(extension);
161
- }
162
- }
163
- }
164
-
165
- /**
166
- *
167
- * @param {string} run_id
168
- * @returns
169
- */
170
- function get_test_beats_report_link(run_id) {
171
- return `${get_base_url()}/reports/${run_id}`;
9
+ const beats = new Beats(config, result);
10
+ await beats.publish();
172
11
  }
173
12
 
174
13
  module.exports = { run }
package/src/cli.js CHANGED
@@ -5,17 +5,21 @@ const sade = require('sade');
5
5
 
6
6
  const prog = sade('testbeats');
7
7
  const publish = require('./commands/publish');
8
+ const logger = require('./utils/logger');
8
9
 
9
10
  prog
10
11
  .version('2.0.1')
11
- .option('-c, --config', 'Provide path to custom config', 'config.json');
12
+ .option('-c, --config', 'Provide path to custom config', 'config.json')
13
+ .option('-l, --logLevel', 'Log Level', "INFO");
12
14
 
13
15
  prog.command('publish')
14
16
  .action(async (opts) => {
15
17
  try {
18
+ logger.setLevel(opts.logLevel);
19
+ logger.info(`Initiating...`);
16
20
  await publish.run(opts);
17
21
  } catch (error) {
18
- console.error(error);
22
+ logger.error(`Report publish failed: ${error.message}`);
19
23
  process.exit(1);
20
24
  }
21
25
  });
@@ -2,14 +2,17 @@ const path = require('path');
2
2
  const trp = require('test-results-parser');
3
3
  const prp = require('performance-results-parser');
4
4
 
5
+ const pkg = require('../../package.json');
5
6
  const { processData } = require('../helpers/helper');
6
7
  const beats = require('../beats');
7
8
  const target_manager = require('../targets');
9
+ const logger = require('../utils/logger');
8
10
 
9
11
  /**
10
12
  * @param {import('../index').PublishOptions} opts
11
13
  */
12
14
  async function run(opts) {
15
+ logger.info(`💡 TestBeats v${pkg.version}`);
13
16
  if (!opts) {
14
17
  throw new Error('Missing publish options');
15
18
  }
@@ -35,6 +38,7 @@ async function run(opts) {
35
38
  validateConfig(config);
36
39
  await processReport(config);
37
40
  }
41
+ logger.info('✅ Results published successfully!');
38
42
  }
39
43
 
40
44
  /**
@@ -42,6 +46,7 @@ async function run(opts) {
42
46
  * @param {import('../index').PublishReport} report
43
47
  */
44
48
  async function processReport(report) {
49
+ logger.debug("processReport: Started")
45
50
  const parsed_results = [];
46
51
  for (const result_options of report.results) {
47
52
  if (result_options.type === 'custom') {
@@ -61,9 +66,10 @@ async function processReport(report) {
61
66
  await target_manager.run(target, result);
62
67
  }
63
68
  } else {
64
- console.log('No targets defined, skipping sending results to targets');
69
+ logger.warn('No targets defined, skipping sending results to targets');
65
70
  }
66
71
  }
72
+ logger.debug("processReport: Ended")
67
73
  }
68
74
 
69
75
  /**
@@ -71,8 +77,9 @@ async function processReport(report) {
71
77
  * @param {import('../index').PublishReport} config
72
78
  */
73
79
  function validateConfig(config) {
80
+ logger.info("🛠️ Validating configuration...")
74
81
  if (!config) {
75
- throw new Error('Missing publish config');
82
+ throw new Error('Missing configuration');
76
83
  }
77
84
  validateResults(config);
78
85
  validateTargets(config);
@@ -83,6 +90,7 @@ function validateConfig(config) {
83
90
  * @param {import('../index').PublishReport} config
84
91
  */
85
92
  function validateResults(config) {
93
+ logger.debug("Validating results...")
86
94
  if (!config.results) {
87
95
  throw new Error('Missing results properties in config');
88
96
  }
@@ -112,6 +120,7 @@ function validateResults(config) {
112
120
  }
113
121
  }
114
122
  }
123
+ logger.debug("Validating results - Successful!")
115
124
  }
116
125
 
117
126
  /**
@@ -119,8 +128,9 @@ function validateResults(config) {
119
128
  * @param {import('../index').PublishReport} config
120
129
  */
121
130
  function validateTargets(config) {
131
+ logger.debug("Validating targets...")
122
132
  if (!config.targets) {
123
- console.warn('targets are not defined in config');
133
+ logger.warn('⚠️ Targets are not defined in config');
124
134
  return;
125
135
  }
126
136
  if (!Array.isArray(config.targets)) {
@@ -150,6 +160,7 @@ function validateTargets(config) {
150
160
  }
151
161
  }
152
162
  }
163
+ logger.debug("Validating targets - Successful!")
153
164
  }
154
165
 
155
166
  module.exports = {
@@ -10,6 +10,7 @@ const ci_info = require('./ci-info');
10
10
  const ai_failure_summary = require('./ai-failure-summary');
11
11
  const { EXTENSION } = require('../helpers/constants');
12
12
  const { checkCondition } = require('../helpers/helper');
13
+ const logger = require('../utils/logger');
13
14
 
14
15
  async function run(options) {
15
16
  const { target, result, hook } = options;
@@ -25,9 +26,9 @@ async function run(options) {
25
26
  try {
26
27
  await extension_runner.run(options);
27
28
  } catch (error) {
28
- console.log('Failed to run extension');
29
- console.log(extension);
30
- console.log(error);
29
+ logger.error(`Failed to run extension: ${error.message}`);
30
+ logger.debug(`Extension details`, extension);
31
+ logger.debug(`Error: `, error);
31
32
  }
32
33
  }
33
34
  }
@@ -1,6 +1,7 @@
1
1
  const { getSuiteHistory, getLastLaunchByName, getLaunchDetails } = require('../helpers/report-portal');
2
2
  const { addChatExtension, addSlackExtension, addTeamsExtension } = require('../helpers/extension.helper');
3
3
  const { HOOK, STATUS } = require('../helpers/constants');
4
+ const logger = require('../utils/logger');
4
5
 
5
6
  async function getLaunchHistory(extension) {
6
7
  const { inputs, outputs } = extension;
@@ -71,8 +72,8 @@ async function run({ extension, target, payload }) {
71
72
  }
72
73
  }
73
74
  } catch (error) {
74
- console.log('Failed to get report portal history');
75
- console.log(error);
75
+ logger.error(`Failed to get report portal history: ${error.message}`);
76
+ logger.debug(`Error: ${error}`);
76
77
  }
77
78
  }
78
79
 
@@ -0,0 +1,98 @@
1
+ const trm = console;
2
+
3
+ const LEVEL_VERBOSE = 2;
4
+ const LEVEL_TRACE = 3;
5
+ const LEVEL_DEBUG = 4;
6
+ const LEVEL_INFO = 5;
7
+ const LEVEL_WARN = 6;
8
+ const LEVEL_ERROR = 7;
9
+ const LEVEL_SILENT = 8;
10
+
11
+ /**
12
+ * returns log level value
13
+ * @param {string} level - log level
14
+ */
15
+ function getLevelValue(level) {
16
+ const logLevel = level.toUpperCase();
17
+ switch (logLevel) {
18
+ case 'TRACE':
19
+ return LEVEL_TRACE;
20
+ case 'DEBUG':
21
+ return LEVEL_DEBUG;
22
+ case 'INFO':
23
+ return LEVEL_INFO;
24
+ case 'WARN':
25
+ return LEVEL_WARN;
26
+ case 'ERROR':
27
+ return LEVEL_ERROR;
28
+ case 'SILENT':
29
+ return LEVEL_SILENT;
30
+ case 'VERBOSE':
31
+ return LEVEL_VERBOSE;
32
+ default:
33
+ return LEVEL_INFO;
34
+ }
35
+ }
36
+
37
+ class Logger {
38
+
39
+ constructor() {
40
+ this.level = process.env.TESTBEATS_LOG_LEVEL || 'INFO';
41
+ this.levelValue = getLevelValue(this.level);
42
+ if (process.env.TESTBEATS_DISABLE_LOG_COLORS === 'true') {
43
+ options.disableColors = true;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * sets log level
49
+ * @param {('TRACE'|'DEBUG'|'INFO'|'WARN'|'ERROR')} level - log level
50
+ */
51
+ setLevel(level) {
52
+ this.level = level;
53
+ this.levelValue = getLevelValue(this.level);
54
+ }
55
+
56
+ trace(...msg) {
57
+ if (this.levelValue <= LEVEL_TRACE) {
58
+ msg.forEach(m => trm.debug(m));
59
+ }
60
+ }
61
+
62
+ debug(...msg) {
63
+ if (this.levelValue <= LEVEL_DEBUG) {
64
+ msg.forEach(m => trm.debug(m));
65
+ }
66
+ }
67
+
68
+ info(...msg) {
69
+ if (this.levelValue <= LEVEL_INFO) {
70
+ msg.forEach(m => trm.info(m));
71
+ }
72
+ }
73
+
74
+ warn(...msg) {
75
+ if (this.levelValue <= LEVEL_WARN) {
76
+ msg.forEach(m => trm.warn(getMessage(m)));
77
+ }
78
+ }
79
+
80
+ error(...msg) {
81
+ if (this.levelValue <= LEVEL_ERROR) {
82
+ msg.forEach(m => trm.error(getMessage(m)));
83
+ }
84
+ }
85
+
86
+ }
87
+
88
+
89
+ function getMessage(msg) {
90
+ try {
91
+ return typeof msg === 'object' ? JSON.stringify(msg, null, 2) : msg;
92
+ } catch (_) {
93
+ return msg;
94
+ }
95
+ }
96
+
97
+
98
+ module.exports = new Logger();