appengineのアプリケーションログをチェックして、ERRORレベルのログがあった場合、管理者にメールで通知するプログラムを作ってみました。
Servletで作ってcronで実行します。
public class LogWatchServlet extends HttpServlet {
private final static Log log = LogFactory.getLog(LogWatchServlet.class);
private final static long duration = 20 * 60 * 1000; // 20min
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
TimeZone.setDefault(TimeZone.getTimeZone("GMT+9:00"));
LogQuery query = LogQuery.Builder
.withDefaults()
.minLogLevel(LogLevel.ERROR)
.majorVersionIds(Arrays.asList(new String[]{SystemProperty.applicationVersion.get().split("\\.")[0]}))
.includeAppLogs(true)
.startTimeMillis(System.currentTimeMillis() - duration);
log.debug("log watch version: " + SystemProperty.applicationVersion.get());
// ログ取得
String lastOffset = null;
List<RequestLogs> logs = new ArrayList<RequestLogs>();
do {
if (lastOffset != null) {
query.offset(lastOffset);
}
int count = 0;
for (RequestLogs record : LogServiceFactory.getLogService().fetch(query)) {
logs.add(record);
lastOffset = record.getOffset();
count++;
}
if (count == 0) {
break;
}
} while(true);
// ログがあったらメール送信
if (! logs.isEmpty()) {
List<String> logbuffer = new ArrayList<String>();
int i=0;
for (RequestLogs record: logs) {
logbuffer.add(format.format(new Date(record.getEndTimeUsec()/1000)) + " " + record.getResource());
for (AppLogLine line: record.getAppLogLines()) {
if (line.getLogLevel().compareTo(LogLevel.ERROR) >= 0) {
logbuffer.add(" " + line.getLogMessage());
}
}
if (i > 50) {
break;
}
i++;
}
logbuffer.add("\n\n全" + logs.size() + "件");
// メール送信
MailService mailService = MailServiceFactory.getMailService();
String sender = "admin@" + AppConst.appId() + ".appspotmail.com";
String subject = "[" + AppConst.appId() + "] applicaton log notification";
MailService.Message message = new MailService.Message(sender, null, subject, StringUtils.join(logbuffer, "\n"));
mailService.sendToAdmins(message);
}
return;
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
[appengine] アプリケーションログをチェックしてメールで通知する はコメントを受け付けていません
これでいいのかしら。。
public static Key allocateId(AsyncDatastoreService ds, String kind)
throws NullPointerException {
if (ds == null) {
throw new NullPointerException("The ds parameter must not be null.");
}
if (kind == null) {
throw new NullPointerException(
"The kind parameter must not be null.");
}
String ns = NamespaceManager.get();
if (ns == null) {
ns = "";
}
String cacheKey = "!"+ns+":"+kind;
Iterator<Key> keys = keysCache.get(cacheKey);
if (keys != null && keys.hasNext()) {
return keys.next();
}
keys =
FutureUtil
.getQuietly(allocateIdsAsync(ds, kind, KEY_CACHE_SIZE))
.iterator();
keysCache.put(cacheKey, keys);
return keys.next();
}
[appengine][slim3] DatastoreUtil#allocateIdがNamespaceに対応するように修正 はコメントを受け付けていません
listプロパティにInMemoryInCriterionを適用するとエラーになるので修正。
これでいいのかしら。。
public boolean accept(Object model) {
Object v = convertValueForDatastore(attributeMeta.getValue(model));
for (Object o : value) {
if (v instanceof Collection<?>) {
for (Object v2: (Collection<?>)v) {
if (compareValue(v2, o) == 0) {
return true;
}
}
} else {
if (compareValue(v, o) == 0) {
return true;
}
}
}
return false;
}
[appengine][slim3]InMemoryInCriterionがlistプロパティでも動作するように修正 はコメントを受け付けていません
忘れがちなのでメモ。
cd appengine-java-sdk-xxx\bin
appcfg.cmd vacuum_indexes c:\path\to\eclipse\project\war
[appengine]不要なインデックスを削除するコマンド はコメントを受け付けていません
Google Appsのユーザー情報を同期しているappengineのアプリケーションでProvisioning APIを使用していましたが、deprecatedになっていました。(2015年で終了。。)
今後は Admin SDK Directory API を使用しろとのことなので、2-legged OAuthを使った使用方法を調査していましたがなかなか情報が見つからずに苦戦。。
試行錯誤の末ようやく成功したのでまとめてみます。
Directory API は ユーザーやグループのUniqueIDも取得できるので、メールアドレスの変更などにも追随できて管理がより楽になります。
- appengine/java で GoogleAppsと連携するアプリケーション
- Google Apps Marketplace に登録してある
- AppsにはMarketplaceからインストールしてある
認証にはMarketplaceのCONSUMER_KEYとCONSUMER_SECRETを使用します。
またAppsの管理者権限があるID(メールアドレス)が必要になります。
Google Apps Marketplaceの ApplicationManifestに以下のスコープが必要になります。
<Scope id="userProvisioningAPI">
<Url>https://www.googleapis.com/auth/admin.directory.user.readonly</Url>
<Reason>This app displays all members in domain.</Reason>
</Scope>
<Scope id="groupProvisioningAPI">
<Url>https://www.googleapis.com/auth/admin.directory.group.readonly</Url>
<Reason>This app displays all groups in domain.</Reason>
</Scope>
まずはOAuthパラメータをセットしてDirectoryAPIインスタンスをビルドします。
2-legged OAuth は1.0にしか対応していないようで、API_KEYも使います。
NetHttpTransport TRANSPORT = new NetHttpTransport();
JacksonFactory JSON_FACTORY = new JacksonFactory();
// The 2-LO authorization section
OAuthHmacSigner signer = new OAuthHmacSigner();
signer.clientSharedSecret = CONSUMER_SECRET;
final OAuthParameters oauthParameters = new OAuthParameters();
oauthParameters.version = "1";
oauthParameters.consumerKey = CONSUMER_KEY;
oauthParameters.signer = signer;
//oauthParameters.signRequestsUsingAuthorizationHeader(transport);
// Directory API構築
directory = new Directory.Builder(
TRANSPORT, JSON_FACTORY, null)
.setApplicationName(APP_NAME)
.setDirectoryRequestInitializer(new DirectoryRequestInitializer(API_KEY))
.setHttpRequestInitializer(oauthParameters)
.build();
ユーザー一覧の取得例です。customKeysにadminアカウントをセットします。
customKeys = new ArrayMap<String,Object>();
customKeys.add("xoauth_requestor_id", ADMIN_ACCOUNT);
Directory.Users.List list = directory.users().list();
list.setUnknownKeys(customKeys);
list.setCustomer("my_customer");
list.setMaxResults(USER_PAGE_MAX);
List<User> result = new ArrayList<User>();
for(;;) {
Users users = list.execute();
for (com.google.api.services.admin.directory.model.User u: users.getUsers()) {
result.add(u);
}
if (users.getNextPageToken() != null) {
list.setPageToken(users.getNextPageToken());
continue;
}
break;
}
setCustomer(“my_customer”); とすると管理下の全てのユーザーが取得できる。
次のページが有るかどうかは、nextPageTokenで判定。
OAuth 2.0 には対応していないようなんですが、どうなんでしょうか。少なくとも今のところ2.0でのやり方を見つけられていません。
Provisioning APIがdeprecatedになったのでDirectory APIに移行してみた はコメントを受け付けていません