Skip to content

Commit

Permalink
feat: detect host id for non-cloud environments (open-telemetry#3575)
Browse files Browse the repository at this point in the history
  • Loading branch information
mwear committed Mar 1, 2023
1 parent 79b06a4 commit 0d373bd
Show file tree
Hide file tree
Showing 14 changed files with 569 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ For experimental package changes, see the [experimental CHANGELOG](experimental/

## Unreleased

* feat: collect host id for non-cloud environments [#3575](https://github.com/open-telemetry/opentelemetry-js/pull/3575) @mwear

### :boom: Breaking Change

### :rocket: (Enhancement)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { DetectorSync, ResourceAttributes } from '../../types';
import { ResourceDetectionConfig } from '../../config';
import { arch, hostname } from 'os';
import { normalizeArch } from './utils';
import { getMachineId } from './machine-id/getMachineId';

/**
* HostDetectorSync detects the resources related to the host current process is
Expand All @@ -31,7 +32,18 @@ class HostDetectorSync implements DetectorSync {
[SemanticResourceAttributes.HOST_NAME]: hostname(),
[SemanticResourceAttributes.HOST_ARCH]: normalizeArch(arch()),
};
return new Resource(attributes);

return new Resource(attributes, this._getAsyncAttributes());
}

private _getAsyncAttributes(): Promise<ResourceAttributes> {
return getMachineId().then(machineId => {
const attributes: ResourceAttributes = {};
if (machineId) {
attributes[SemanticResourceAttributes.HOST_ID] = machineId;
}
return attributes;
});
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as child_process from 'child_process';
import * as util from 'util';

export const execAsync = util.promisify(child_process.exec);
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as fs from 'fs/promises';
import { execAsync } from './execAsync';
import { diag } from '@opentelemetry/api';

export async function getMachineId(): Promise<string> {
try {
const result = await fs.readFile('/etc/hostid', { encoding: 'utf8' });
return result.trim();
} catch (e) {
diag.debug(`error reading machine id: ${e}`);
}

try {
const result = await execAsync('kenv -q smbios.system.uuid');
return result.stdout.trim();
} catch (e) {
diag.debug(`error reading machine id: ${e}`);
}

return '';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { execAsync } from './execAsync';
import { diag } from '@opentelemetry/api';

export async function getMachineId(): Promise<string> {
try {
const result = await execAsync('ioreg -rd1 -c "IOPlatformExpertDevice"');

const idLine = result.stdout
.split('\n')
.find(line => line.includes('IOPlatformUUID'));

if (!idLine) {
return '';
}

const parts = idLine.split('" = "');
if (parts.length === 2) {
return parts[1].slice(0, -1);
}
} catch (e) {
diag.debug(`error reading machine id: ${e}`);
}

return '';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as fs from 'fs/promises';
import { diag } from '@opentelemetry/api';

export async function getMachineId(): Promise<string> {
const paths = ['/etc/machine-id', '/var/lib/dbus/machine-id'];

for (const path of paths) {
try {
const result = await fs.readFile(path, { encoding: 'utf8' });
return result.trim();
} catch (e) {
diag.debug(`error reading machine id: ${e}`);
}
}

return '';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { diag } from '@opentelemetry/api';

export async function getMachineId(): Promise<string> {
diag.debug('could not read machine-id: unsupported platform');
return '';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as process from 'process';
import { execAsync } from './execAsync';
import { diag } from '@opentelemetry/api';

export async function getMachineId(): Promise<string> {
const args =
'QUERY HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography /v MachineGuid';
let command = '%windir%\\System32\\REG.exe';
if (process.arch === 'ia32' && 'PROCESSOR_ARCHITEW6432' in process.env) {
command = '%windir%\\sysnative\\cmd.exe /c ' + command;
}

try {
const result = await execAsync(`${command} ${args}`);
const parts = result.stdout.split('REG_SZ');
if (parts.length === 2) {
return parts[1].trim();
}
} catch (e) {
diag.debug(`error reading machine id: ${e}`);
}

return '';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as process from 'process';

let getMachineId: () => Promise<string>;

switch (process.platform) {
case 'darwin':
({ getMachineId } = require('./getMachineId-darwin'));
break;
case 'linux':
({ getMachineId } = require('./getMachineId-linux'));
break;
case 'freebsd':
({ getMachineId } = require('./getMachineId-bsd'));
break;
case 'win32':
({ getMachineId } = require('./getMachineId-win'));
break;
default:
({ getMachineId } = require('./getMachineId-unsupported'));
}

export { getMachineId };
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,16 @@ describeNode('hostDetector() on Node.js', () => {

it('should return resource information about the host', async () => {
const os = require('os');
const mid = require('../../../src/platform/node/machine-id/getMachineId');

const expectedHostId = 'f2c668b579780554f70f72a063dc0864';

sinon.stub(os, 'arch').returns('x64');
sinon.stub(os, 'hostname').returns('opentelemetry-test');
sinon.stub(mid, 'getMachineId').returns(Promise.resolve(expectedHostId));

const resource: IResource = await hostDetector.detect();
await resource.waitForAsyncAttributes?.();

assert.strictEqual(
resource.attributes[SemanticResourceAttributes.HOST_NAME],
Expand All @@ -41,6 +46,10 @@ describeNode('hostDetector() on Node.js', () => {
resource.attributes[SemanticResourceAttributes.HOST_ARCH],
'amd64'
);
assert.strictEqual(
resource.attributes[SemanticResourceAttributes.HOST_ID],
expectedHostId
);
});

it('should pass through arch string if unknown', async () => {
Expand All @@ -55,4 +64,29 @@ describeNode('hostDetector() on Node.js', () => {
'some-unknown-arch'
);
});

it('should handle missing machine id', async () => {
const os = require('os');
const mid = require('../../../src/platform/node/machine-id/getMachineId');

sinon.stub(os, 'arch').returns('x64');
sinon.stub(os, 'hostname').returns('opentelemetry-test');
sinon.stub(mid, 'getMachineId').returns(Promise.resolve(''));

const resource: IResource = await hostDetector.detect();
await resource.waitForAsyncAttributes?.();

assert.strictEqual(
resource.attributes[SemanticResourceAttributes.HOST_NAME],
'opentelemetry-test'
);
assert.strictEqual(
resource.attributes[SemanticResourceAttributes.HOST_ARCH],
'amd64'
);
assert.strictEqual(
false,
SemanticResourceAttributes.HOST_ID in resource.attributes
);
});
});
Loading

0 comments on commit 0d373bd

Please sign in to comment.