本指南演示了如何从 Node.js Web 应用程序中提取日志并将它们安全地传送到 Elasticsearch Service 部署中。 你将设置 Filebeat 来监控具有标准 Elastic Common Schema (ECS) 格式字段的 JSON 结构化日志文件,然后你将在向 Node.js 服务器发出请求时查看 Kibana 中日志事件的实时可视化。 虽然此示例使用了 Node.js,但这种监视日志输出的方法适用于许多客户端类型。 检查可用 ECS 日志记录插件的列表。
在如下的演示中,我将使用最新的 Elastic Stack 8.5.0 来进行展示。
使用 Filebeat 从 Node.js Web 应用程序提取日志
如果你还没有安装好自己的 Elasticsearch 及 Kibana,请参阅我之前的文章:
在安装时,请参考最新的 Elastic Stack 8.x 的指南来进行安装。
你将设置一个简单的 Node.js Web 服务器和客户端应用程序。 查看 Node.js 下载页面以获取安装说明。
提示:对于以下三个包,你可以创建一个工作目录以使用 Node 包管理器 (NPM) 安装这些包。 然后,你可以从同一目录运行你的 Node.js 网络服务器和客户端,以便它可以使用这些包。 或者,你也可以通过运行带有 -g 选项的 Node 包安装命令来全局安装 Node 包。 有关详细信息,请参阅 NPM 包安装说明。
这是一个流行的 Node.js 日志记录包。 创建一个新的本地目录并运行以下命令以在其中安装 winston:
npm install winston
$ pwd
/Users/liuxg/nodejs/nodejs_logs
$ npm install winston
npm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/
npm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/
added 29 packages, and removed 1 package in 8s
2 packages are looking for funding
run `npm fund` for details
npm notice
npm notice New major version of npm available! 8.9.0 -> 9.1.1
npm notice Changelog: https://github.com/npm/cli/releases/tag/v9.1.1
npm notice Run npm install -g npm@9.1.1 to update!
npm notice
这个是针对 Nodejs winston 日志记录器的。此插件将你的 Node.js 日志格式化为 ECS 结构化 JSON 格式,非常适合摄取到 Elasticsearch 中。 要安装 ECS winston 记录器,请在你的工作目录中运行以下命令,以便将包安装在与 winston 包相同的位置:
npm install @elastic/ecs-winston-format
$ pwd
/Users/liuxg/nodejs/nodejs_logs
$ npm install @elastic/ecs-winston-format
npm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/
npm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/
added 12 packages in 8s
3 packages are looking for funding
run `npm fund` for details
Got 是一个“人性化且功能强大的 Node.js HTTP 请求库”。 - 该插件可用于查询教程中使用的示例 Web 服务器。 要安装 Got 软件包,请在你的工作目录中运行以下命令:
npm install got
$ pwd
/Users/liuxg/nodejs/nodejs_logs
$ npm install got
npm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/
npm notice Beginning October 4, 2021, all connections to the npm registry - including for package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog for more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/
added 22 packages in 7s
13 packages are looking for funding
run `npm fund` for details
要连接到 Elasticsearch 集群、将数据流式传输到 Elasticsearch 并发出查询,你需要考虑身份验证。 支持两种身份验证机制,API 密钥和基本身份验证。 在这里,为了让你快速入门,我们将向你展示如何使用基本身份验证,但你也可以生成 API 密钥,如下所示。 API 密钥更安全,更适合生产环境。
如果你对如何连接到 Elasticsearch 还不是很清楚的话,请参阅我之前的文章 “Elasticsearch:使用最新的 Nodejs client 8.x 来创建索引并搜索”。在本篇文章中,我就不再赘述了。
接下来,创建一个运行 Web 服务器并记录 HTTP 请求的基本 Node.js 脚本。
在安装 winston 和 ECS 格式化程序包的同一本地目录中,创建一个新文件 webserver.js 并将其保存为以下内容:
webserver.js
$ pwd
/Users/liuxg/nodejs/nodejs_logs
$ ls
webserver.js
$ cat webserver.js
const http = require('http')
const winston = require('winston')
const ecsFormat = require('@elastic/ecs-winston-format')
const logger = winston.createLogger({
level: 'debug',
format: ecsFormat({ convertReqRes: true }),
transports: [
//new winston.transports.Console(),
new winston.transports.File({
//path to log file
filename: 'logs/log.json',
level: 'debug'
})
]
})
const server = http.createServer(handler)
server.listen(3000, () => {
logger.info('listening at http://localhost:3000')
})
function handler (req, res) {
res.setHeader('Foo', 'Bar')
res.end('ok')
logger.info('handled request', { req, res })
}
此 Node.js 脚本在 http://localhost:3000 运行 Web 服务器,并使用 winston 记录器根据 HTTP 请求将记录事件发送到文件 log.json。
接下来,我们使用如下的命令来运行上面的 Node.js 脚本:
node webserver.js
脚本运行后,打开 Web 浏览器访问 http://localhost:3000,应该会出现一条简单的 ok 消息。

在你创建 webserver.js 的目录中,你现在应该找到一个新创建的 log.json 文件。 打开文件并检查内容。 应该有一个日志条目表明 Node.js 正在侦听 localhost 端口,而另一个条目则表明您在浏览器中打开 localhost 时的 HTTP 请求。
暂时让 webserver.js 运行,我们将向它发送一些 HTTP 请求。
$ pwd
/Users/liuxg/nodejs/nodejs_logs
$ ls
logs webserver.js
$ cd logs/
$ ls
log.json
$ cat log.json
{"@timestamp":"2022-11-14T02:47:32.522Z","log.level":"info","message":"listening at http://localhost:3000","ecs":{"version":"1.6.0"}}
{"@timestamp":"2022-11-14T02:49:21.182Z","log.level":"info","message":"handled request","ecs":{"version":"1.6.0"},"http":{"version":"1.1","request":{"method":"GET","headers":{"host":"localhost:3000","connection":"keep-alive","sec-ch-ua":"\"Google Chrome\";v=\"107\", \"Chromium\";v=\"107\", \"Not=A?Brand\";v=\"24\"","sec-ch-ua-mobile":"?0","sec-ch-ua-platform":"\"macOS\"","upgrade-insecure-requests":"1","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9","sec-fetch-site":"none","sec-fetch-mode":"navigate","sec-fetch-user":"?1","sec-fetch-dest":"document","accept-encoding":"gzip, deflate, br","accept-language":"en,zh-CN;q=0.9,zh;q=0.8,zh-TW;q=0.7,fr;q=0.6,da;q=0.5,de;q=0.4,my;q=0.3,es;q=0.2","cookie":"_st_togo_session=VVc4QnJac1FCeUlpcm9UT0dtWTBYVmVZYkRQbEpnaGJhUGFGQVhpbGwzeVUvUG5SK0pXK3BLOXNQejhiZGE1bForYUQvNkNWS0JQTDVjL2tqYjl5SVhTeFpwNjN2OTk1Q3VpWkFjRDRxS0ttZkhRR3VCaFhhSVBHZS9CRS81NjJzYk5uUWxYVmxBc2s0VzViUDFtaExMb2NNOEs2Y1IrR3l5QmFUNWx3VFViYXEzdjkweTdXbXFtWDQ1L3hIaEdwdWZCRExCcmlscTRuTDlSMTFocFlnUT09LS11SUZyNWpvODlMM3R2S3JSZytWVEhRPT0%3D--bf4dbdb1387208eb9c2bf8b7fe88729532880187; session=eyJjc3JmX3Rva2VuIjoiMTk5ZWM0NzQxNmM1NmY3YTMxYWVhMmVhMTMzNDc3NTc1ZTgzZTZiZSJ9.YwTRag.sYZBSm7Gjku9nGLRw4jw3NAqcgg; sid=Fe26.2**eaf53f0d84a6ecdb87b0646461e906103275daeb125e9a3cdd99038e607dac17*-UEJP8vtsqjk_MYYh0afqQ*2S30rxbYeq-OQh-eNGteqtUVEr8p1Oklg89VWBT4nraxupoKyw6XwYcbdd8J5OxJBrPbfanCOB0iFACq0Ul2Hw_tzikogeiZuodS6kXVreaLViC0gMSLrYPw3Ysk7EbFdEF9G9ZpgYW6ryOSLDuETGiByCnhJ9cT58Aq8zDSPO-ZkfHShYyL1ZXsnM_TmslVn6cpZSP2c6sS-Khi7LVieMbCrd2WTo3NZBUQZ-fmkml26hA921sIHFiCMq91oMuo**3254f523a2ec4d74cac2e09bd74d561c91a54a7e78c4e1ed6e9eee1ac27d546d*OKIM_of3Bl2LWc4FHKCCLOzMut7R-y43Gbnla4gBB-s"}},"response":{"status_code":200,"headers":{"foo":"Bar"}}},"url":{"path":"/","full":"http://localhost:3000/"},"client":{"address":"::1","ip":"::1","port":58232},"user_agent":{"original":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"}}
{"@timestamp":"2022-11-14T02:49:21.379Z","log.level":"info","message":"handled request","ecs":{"version":"1.6.0"},"http":{"version":"1.1","request":{"method":"GET","headers":{"host":"localhost:3000","connection":"keep-alive","sec-ch-ua":"\"Google Chrome\";v=\"107\", \"Chromium\";v=\"107\", \"Not=A?Brand\";v=\"24\"","sec-ch-ua-mobile":"?0","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36","sec-ch-ua-platform":"\"macOS\"","accept":"image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8","sec-fetch-site":"same-origin","sec-fetch-mode":"no-cors","sec-fetch-dest":"image","referer":"http://localhost:3000/","accept-encoding":"gzip, deflate, br","accept-language":"en,zh-CN;q=0.9,zh;q=0.8,zh-TW;q=0.7,fr;q=0.6,da;q=0.5,de;q=0.4,my;q=0.3,es;q=0.2","cookie":"_st_togo_session=VVc4QnJac1FCeUlpcm9UT0dtWTBYVmVZYkRQbEpnaGJhUGFGQVhpbGwzeVUvUG5SK0pXK3BLOXNQejhiZGE1bForYUQvNkNWS0JQTDVjL2tqYjl5SVhTeFpwNjN2OTk1Q3VpWkFjRDRxS0ttZkhRR3VCaFhhSVBHZS9CRS81NjJzYk5uUWxYVmxBc2s0VzViUDFtaExMb2NNOEs2Y1IrR3l5QmFUNWx3VFViYXEzdjkweTdXbXFtWDQ1L3hIaEdwdWZCRExCcmlscTRuTDlSMTFocFlnUT09LS11SUZyNWpvODlMM3R2S3JSZytWVEhRPT0%3D--bf4dbdb1387208eb9c2bf8b7fe88729532880187; session=eyJjc3JmX3Rva2VuIjoiMTk5ZWM0NzQxNmM1NmY3YTMxYWVhMmVhMTMzNDc3NTc1ZTgzZTZiZSJ9.YwTRag.sYZBSm7Gjku9nGLRw4jw3NAqcgg; sid=Fe26.2**eaf53f0d84a6ecdb87b0646461e906103275daeb125e9a3cdd99038e607dac17*-UEJP8vtsqjk_MYYh0afqQ*2S30rxbYeq-OQh-eNGteqtUVEr8p1Oklg89VWBT4nraxupoKyw6XwYcbdd8J5OxJBrPbfanCOB0iFACq0Ul2Hw_tzikogeiZuodS6kXVreaLViC0gMSLrYPw3Ysk7EbFdEF9G9ZpgYW6ryOSLDuETGiByCnhJ9cT58Aq8zDSPO-ZkfHShYyL1ZXsnM_TmslVn6cpZSP2c6sS-Khi7LVieMbCrd2WTo3NZBUQZ-fmkml26hA921sIHFiCMq91oMuo**3254f523a2ec4d74cac2e09bd74d561c91a54a7e78c4e1ed6e9eee1ac27d546d*OKIM_of3Bl2LWc4FHKCCLOzMut7R-y43Gbnla4gBB-s"}},"response":{"status_code":200,"headers":{"foo":"Bar"}}},"url":{"path":"/favicon.ico","full":"http://localhost:3000/favicon.ico"},"client":{"address":"::1","ip":"::1","port":58232},"user_agent":{"original":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"}}
在此步骤中,你将创建一个向你的 Web 服务器发送 HTTP 请求的 Node.js 应用程序。
在你的工作目录中,创建一个文件 webrequests.js 并使用以下内容保存它:
webrequests.js
// const got = require('got');
import got from 'got';
const addresses = [
'aardvark@the.zoo',
'crocodile@the.zoo',
'elephant@the.zoo',
'emu@the.zoo',
'hippopotamus@the.zoo',
'llama@the.zoo',
'octopus@the.zoo',
'otter@the.zoo',
'panda@the.zoo',
'pangolin@the.zoo',
'tortoise@the.zoo',
'walrus@the.zoo'
];
const method = [
'get',
'put',
'post'
];
async function sleep(millis) {
return new Promise(resolve => setTimeout(resolve, millis));
}
(async () => {
while (true) {
var type = Math.floor(Math.random() * method.length);
var email = Math.floor(Math.random() * addresses.length);
var sleeping = Math.floor(Math.random() * 9) + 1;
switch (method[type]) {
case 'get':
try {
const response = await got.get('http://localhost:3000/', {
headers: {
from: addresses[email]
}
}).json();
console.log(response.body);
} catch (error) {
// console.log(error.response);
}
break; // end case 'get'
case 'put':
try {
const response = await got.put('http://localhost:3000/', {
headers: {
from: addresses[email]
}
}).json();
console.log(response.body);
} catch (error) {
// console.log(error.response);
}
break; // end case 'put'
case 'post':
try {
const {
data
} = await got.post('http://localhost:3000/', {
headers: {
from: addresses[email]
}
}).json();
console.log(data);
} catch (error) {
// console.log(error.response);
}
break; // end case 'post'
} // end switch on method
await sleep(sleeping * 1000);
}
})();
$ pwd
/Users/liuxg/nodejs/nodejs_logs
$ ls
logs webrequests.mjs webserver.js
此 Node.js 应用程序使用 GET、POST 或 PUT 类型的随机方法生成 HTTP 请求,并使用各种假冒的电子邮件地址从请求标头中随机生成 HTTP 请求。 请求以 1 到 10 秒之间的随机间隔发送。
Got 包用于发送请求,它们被定向到位于 http://localhost:3000 的 Web 服务器。 要了解如何发送自定义标头(例如本示例中使用的 from 字段),请查看 Got 文档中的标头。
我们接下来,在一个新的 terminal 中,打入如下的命令运行上面的脚本:

脚本运行大约 30 秒后,输入 CTRL + C 停止它。 查看您的 Node.js logs/log.json 文件。 它应该包含一些像这样的条目:

{"@timestamp":"2022-11-14T03:34:00.667Z","log.level":"info","message":"handled request","ecs":{"version":"1.6.0"},"http":{"version":"1.1","request":{"method":"PUT","headers":{"user-agent":"got (https://github.com/sindresorhus/got)","from":"panda@the.zoo","accept":"application/json","accept-encoding":"gzip, deflate, br","host":"localhost:3000","connection":"keep-alive","content-length":"0"},"body":{"bytes":0}},"response":{"status_code":200,"headers":{"foo":"Bar"}}},"url":{"path":"/","full":"http://localhost:3000/"},"client":{"address":"::1","ip":"::1","port":59548},"user_agent":{"original":"got (https://github.com/sindresorhus/got)"}}
每个日志条目都包含 HTTP 请求的详细信息。 特别是,在此示例中,你可以找到请求的时间戳、PUT 类型的请求方法以及来自电子邮件地址 octopus@the.zoo 的请求头。 由于请求类型和电子邮件地址是随机生成的,因此你的示例可能会有所不同。
将你的日志以带有 ECS 字段的 JSON 格式编写,便于解析和分析,并与其他应用程序进行标准化。 随着日志中捕获的数据量和类型随着时间的推移而扩展,标准、易于解析的格式变得越来越重要。
确认 webserver.mjs 和 webrequests.js 都按预期运行后,输入 CTRL + C 停止 Node.js 脚本,同时删除 log.json。
Filebeat 提供了一种简单、易于配置的方式来监控您的 Node.js 日志文件并将日志数据移植到 Elasticsearch 服务中。
下载 Filebeat 并将其解压缩到你要从中收集数据的本地服务器上。我们需要做如下的配置。你需要根据自己的安装做相应的修改:
filebeat.yml

我们在 filebeat.yml 文件的开始部分添加如上的部分。
我们接下来添加 JSON input 的选项部分:
Filebeat 的输入配置选项包括几个用于解码 JSON 消息的设置。 日志文件是逐行解码的,因此每行包含一个 JSON 对象很重要。

我们接下来还需要配置 Elasticsearch 的输出部分:

在上面,我们需要根据自己的用户名及密码进行相应的修改。特别需要指出的是,我们在生产的环境中,千万不要使用上面所示的使用超级用户的账号。另外,我们建议使用 API Key 来进行访问会更好。我们也需要根据自己的 Elasticsearch 的证书路径来做相应的修改。
保存好上面的配置文件,我们使用如下的命令来进行测试配置是否有问题:
$ pwd
/Users/liuxg/elastic/filebeat-8.5.0-darwin-aarch64
$ ls
LICENSE.txt fields.yml filebeat.yml modules.d
NOTICE.txt filebeat kibana
README.md filebeat.reference.yml module
$ ./filebeat test config
Config OK
$ ./filebeat test output
elasticsearch: https://localhost:9200...
parse url... OK
connection...
parse host... OK
dns lookup... OK
addresses: ::1, 127.0.0.1
dial up... OK
TLS...
security: server's certificate chain verification is enabled
handshake... OK
TLS version: TLSv1.3
dial up... OK
talk to server... OK
version: 8.5.0
如上所示,我们的配置是没有任何问题的(语法层面),另外,我们的 Elasticsearch 的输出配置也是没有任何问题的。
Filebeat 带有用于解析、索引和可视化数据的预定义 assets。 要加载这些 assets,请从 Filebeat 安装目录运行以下命令:
./filebeat setup -e

重要: 根据安装位置、环境和本地权限等变量,你可能需要更改 filebeat.yml 的所有权。 你还可以尝试以 root 身份运行该命令:sudo ./filebeat setup -e 或者你可以通过使用 --strict.perms=false 选项运行该命令来禁用严格权限检查。

设置过程需要几分钟。 如果一切顺利,你应该会收到一条确认消息:
Loaded Ingest pipelines
Filebeat 数据视图现在在 Elasticsearch 中可用。 验证:


为了提高安全性,你可以通过 Elasticsearch 控制台生成 Elasticsearch API 密钥,而不是使用基本身份验证,然后将 Filebeat 配置为使用新密钥安全地连接到 Elasticsearch 部署中。
我们在 Kibana 的控制台中打入如下的命令:
POST /_security/api_key
{
"name": "filebeat-api-key",
"role_descriptors": {
"logstash_read_write": {
"cluster": [
"manage_index_templates",
"monitor"
],
"index": [
{
"names": [
"filebeat-*"
],
"privileges": [
"create_index",
"write",
"read",
"manage"
]
}
]
}
}
}

这将创建一个具有集群监控权限的 API 密钥,它提供只读访问权限以确定集群状态,以及 manage_index_templates 允许对索引模板的所有操作。 一些额外的权限还允许对指定索引进行创建索引、写入和管理操作。 添加了索引管理权限以启用索引刷新。
将您的 API 密钥信息添加到 filebeat.yml 的 Elasticsearch 输出部分,就在 output.elasticsearch: 下方。 使用格式 <id>:<api_key>。 如果你的结果如本示例所示,请输入 KCJNdIQB8eIGGIhj_MkN:zpeQptZ6Rdmo4kQmyvKLYg。
我们可以在 filebeat.yml 中做如下的配置:

很显然,这样做的好处是我们不暴露自己用户的密码,而且我们可以为这个 API key 设置权限及时间期限。当然我们也可以在 Kibana 中随时删除这个 API key,如果我们不想让这个 API key 继续起作用的话。
是时候向 Elasticsearch 发送一些日志数据了!通过从 Filebeat 安装目录运行以下命令来启动 Filebeat:
./filebeat -e -c filebeat.yml
在上面的命令中:
注意:万一该命令无法按预期工作,请查看 Filebeat 快速入门以了解适用于您操作系统的详细命令语法。 你也可以尝试以 root 身份运行命令:sudo ./filebeat -e -c filebeat.yml。

Filebeat 现在应该正在运行并监控 log.json 的内容,实际上它还不存在。 所以,让我们创建它。 打开一个新的终端实例并运行 webserver.js Node.js 脚本:
node webserver.js
接下来,运行 Node.js webrequests.mjs 脚本以向 Node.js Web 服务器发送随机请求。
node webrequests.mjs
让脚本运行几分钟,然后冲泡一杯咖啡或茶☕。 之后,确保 log.json 文件按预期生成并填充了多个日志条目。
我们首先在 Kibana 的控制台中,查看有没有数据进入到 Elasticsearch:
GET filebeat-8.5.0/_count
上面的命令会显示:
{
"count": 34,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
}
}
我们也可以在 Kibana 中的 Discover 来查看数据:


现在是时候根据应用程序日志数据创建可视化了。我们按照如下的步骤来进行:






我们可以通过拖拽左边的字段到 visualization builder 来创建如上所示的可视化:

选择 Save and return 按钮:

我们接下来创建第二个可视化图:



我们接下来创建第三个可视化图:







你现在拥有一个具有三个可视化效果的 Kibana 仪表板:一个显示每个 HTTP 请求方法随时间变化的频率的堆叠条形图,另一个显示来自标头的各种 HTTP 随时间变化的频率的堆叠条形图,以及一个显示每个 HTTP 请求方法的相对频率的 Donut HTTP 请求方法类型。
你可以为可视化添加标题,根据需要调整它们的大小和位置,然后保存更改。
在 Kibana 仪表板上选择 refresh。 由于应用程序 webrequests.mjs 继续运行并向 Node.js 服务器发送 HTTP 请求,因此 webserver.js 继续生成日志数据,并且每次页面刷新时,你的 Kibana 可视化都会使用该数据进行更新。

作为最后一步,请记住停止 Filebeat、Node.js Web 服务器和客户端。 在每个应用程序的终端窗口中输入 CTRL + C 以停止它们。
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl