くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Djangoでログをカスタマイズして、レスポンス時の詳細をロギングする

Djangoを利用している際に、標準のロギングだとリクエスト時のパラメタが表示されない。。
POSTだとURLしか確認できないため、拡張してみたときの備忘録。

リクエスト/レスポンスに関する拡張ポイント

Djangoでは、色々と拡張ポイントが用意されているらしく、
Middlewareを作ってその拡張ポイントを利用することができる。

今回調べたのはこんな感じ。ほかにもprocess_template_responseとかprocess_viewとかあるらしい。

# -*- coding: utf-8 -*
class LoggingRequestMiddleware(object):

  """ リクエスト時のハンドリング """
  def process_request(self, request):
  
  """ リクエスト時に例外発生時のハンドリング """
  def process_exception(self, request, exception)
  
  """ レスポンス返却時のハンドリング """
  def process_response(self, request, response):
    return response

レスポンス時のステータスとリクエスト詳細をログに表示する

ログを出力するMiddlewareを作成する

process_responseを拡張して、パラメタやログインユーザも表示する用に変更してみた。

# -*- coding: utf-8 -*
import logging
import traceback

class LoggingRequestMiddleware(object):
    logger = logging.getLogger('django.request')

    def process_response(self, request, response):
        try:
            request_info = self.__get_request_info(request)
            user_info = self.__get_user_info__(request.user)

            msg = u"{} {} \t{}".format(response.status_code, request_info, user_info)
            self.logger.info(msg)
        except:
            self.logger.error(traceback.format_exc())

        return response

    def __get_request_info(self, request):
        if request.method == u'GET':
            params = self.__format_params__(dict(request.GET))
        elif request.method == u'POST':
            params = self.__format_params__(dict(request.POST))
        else:
            params = u''

        return u'{} {}\tparam:[{}]'.format(request.method, request.get_full_path(), params)

    def __get_user_info__(self, user):
        if user is None:
            return u'user: - '

        try:
            return u'user: {}({})'.format(user, user.id)
        except:
            return u'user: - '

    def __format_params__(self, params):
        param_items = filter(lambda (k, v):k != u'csrfmiddlewaretoken',params.items())
        return u', '.join([u'{}={}'.format(key, self.__list_str__(value)) for (key, value) in param_items])

    def __list_str__(self, params):
        if params is None:
            return u''
        elif len(params) == 1:
            return u'{}'.format(params[0])
            
        return u'[' + u', '.join(u'{}'.format(item) for item in params) + u']'

作成したMiddlewareを利用するようにsettings.pyを修正する

作ったMiddlewareを有効にするには、MIDDLEWARE_CLASSESに追加すればOK!

MIDDLEWARE_CLASSES = [
   ・・・
    'app.middle.LoggingRequestMiddleware'
]

以上!!

参考にしたサイト様

Androidのエミュレータでネットワークを使えるようにする

エミュレータを動かしているPCにGoogleDNSを登録する必要があるらしい。

GoogleのDSNは、以下の2つ

  • 8.8.8.8
  • 8.8.4.4

参考にしたサイト様

Androidで電話の発信・切断・状態の確認

Androidで電話を操作するアプリを作る機会があったので、その時の備忘録。

発信する

  • 発信する場合は簡単。Intentを発行するだけでOK。
Context context = ...;
String phoneNumber = "090-0000-9999"
Uri uri = Uri.parse("tel:" + phoneNumber);
Intent intent = new Intent(Intent.ACTION_CALL, uri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // Activity以外から呼ぶ場合に必要
context.startActivity(intent);

切断する

  • 切断する場合は、APIが公開されていないため、複雑
  • 非公開APIなので、今後使えなくなる可能性もある
  • また、電話を扱う場合は、パーミッションも必要
<uses-permission android:name="android.permission.CALL_PHONE"/>

1. 非公開インターフェースITelephonyを自前で用意する

package com.android.internal.telephony;

public interface ITelephony {
    boolean endCall(); // 切断
    void answerRingingCall(); // 応答
    void silenceRinger(); // 着信音を消す
}

2. リフレクションを使って、ITelephonyのインスタンスを取得

// TelephonyManagerを取得
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);

// protectedなTelephonyManager.getITelephony()のアクセス修飾子を変更して、呼び出す
Class<?> c = Class.forName(telephonyManager.getClass().getName());
Method m = c.getDeclaredMethod("getITelephony");
m.setAccessible(true);
ITelephony telephony = (ITelephony) m.invoke(telephonyManager);

3. 取得したITelephonyのインスタンスで切断

telephony.endCall(); // 切断

TelephonyManagerまではソースが見れるので、そこからいろいろ見てみると良いかも。

電話の状態を確認する

  • PhoneStateListenerが用意されているので、extendする
  • 電話番号も取得したい場合には、パーミッションが必要
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
  • 電話の状態を確認するには、PhoneStateListenerのonCallStateChangedをOverrideする
// TelephonyManagerを取得
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);

// listenerを登録
telephonyManager.listen(new PhoneStateListener(){
    // PhoneStateListenerの`onCallStateChanged`をOverride
    @Override
    public void onCallStateChanged(int state, String incomingNumber) {
        // パーミッションがない場合、incomingNumberは常に空
        switch (state) {
          case TelephonyManager.CALL_STATE_RINGING:
            /* 着信 */
            Log.i(TAG, "Call State Changed: CALL_STATE_RINGING");
            break;
        case TelephonyManager.CALL_STATE_OFFHOOK:
            /* 通話 */
            Log.i(TAG, "Call State Changed: CALL_STATE_OFFHOOK");
            break;
        case TelephonyManager.CALL_STATE_IDLE:
            /* 待受 */
            Log.i(TAG, "Call State Changed: CALL_STATE_IDLE");
            break;
        }
    }
}, PhoneStateListener.LISTEN_CALL_STATE);

以上!!

参考にしたサイト様

Androidで常駐サービスを作ろう(Service/WakefulBroadcastReceiver/Doze/AlarmManager)

Androidで常駐起動するサービスを作ることがあったので、そのときの備忘録。
作るときのポイントを整理してみた。が、いろいろ考えることが多い。。。

ポイント

  1. WakefulBroadcastReceiverを使ってServiceを起動する
  2. スリープしてもWifiの接続を維持しておく
  3. AlarmManagerで定期的に起動する
  4. ネットワークを使うときは、電源の最適化をOFFにする

もろもろ

  1. Android6.0(API 23)からDozeが追加された
  2. DozeモードになるとCPUを使わなくなり、KILLされやすくなる
  3. Dozeモードになるとネットワークを使わなくなる

WakefulBroadcastReceiverを使って、Wakelockを取得する

  1. AndroidManifest.xmlパーミッションを追加
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
  1. AndroidManifest.xmlにRecieverを追加
<receiver android:name=".StartupReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
        <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</receiver>
  1. WakefulBroadcastReceiverを継承したRecieverを用意して、そこからサービスを起動
public class StartupReceiver extends WakefulBroadcastReceiver {
    private static final String TAG = StartupReceiver.class.getSimpleName();

    @Override
    public void onReceive(Context context, Intent intent) {
        Intent service = new Intent(context, MyService.class);
        startWakefulService(context, service);
    }
}

スリープ時にWifiの接続を維持する

    try {
        ContentResolver contentResolver = getContentResolver();
        // Settings.System.WIFI_SLEEP_POLICY は API level 17 から deprecated なので
        // Settings.Global.WIFI_SLEEP_POLICY を使用する
        int policy = Settings.System.getInt(contentResolver, Settings.Global.WIFI_SLEEP_POLICY);
        if (policy != Settings.Global.WIFI_SLEEP_POLICY_NEVER) {
            Settings.System.putInt(contentResolver, Settings.Global.WIFI_SLEEP_POLICY, Settings.Global.WIFI_SLEEP_POLICY_NEVER);
        }
    } catch (Settings.SettingNotFoundException e) {
        Log.i(TAG, e.getLocalizedMessage(), e);
    }

ネットワークが有効化を確認する

ConnectivityManagerを使うと、通信ができているかどうか確認できる。

public boolean isNetworkAvailable() {
    ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
    boolean isAvailable = activeNetwork != null && activeNetwork.isConnectedOrConnecting();
    if (isAvailable) {
        Log.i(TAG, String.format("Network is Available: %s %s",
                activeNetwork.getTypeName(), activeNetwork.getSubtypeName()
        ));
    } else {
        String info = "";
        if (activeNetwork != null) {
            info = String.format(": %s %s %s", activeNetwork.getState().name(), activeNetwork.getTypeName(), activeNetwork.getSubtypeName());
        }
        Log.w(TAG, "Network is Not Working" + info);
    }
    return isAvailable;
}

AlarmMangerで定期的に起動しておく

private void setupAlarm() {
    Context context = ...;
    // AlarmManagerを取得する
    AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

    // Alarmのタイプ
    int type = AlarmManager.RTC_WAKEUP;

    // Alarmの開始日時
    long triggerAtMillis = Calendar.getInstance().getTimeInMillis();

    // Alarmの繰り返し間隔
    long intervalMillis = 60 * 1000; // 1min

    // Alarmの発火時に発行するIntent
    int requestCode = 0;
    Intent intent = new Intent(context, StartupReceiver.class);
    int flags = PendingIntent.FLAG_UPDATE_CURRENT;
    PendingIntent operation = PendingIntent.getBroadcast(context, requestCode, intent, flags);

    // AlarmManagerに登録する
    alarmManager.setRepeating(type, triggerAtMillis, intervalMillis, operation);
}

電源の最適化をOFFにする(Dozeのホワイトリストに登録する)

  1. AndroidManifest.xmlパーミッションを追加
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
  1. PowerManagerを使ってホワイトリストに入っているか確認&リクエス
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
    String packageName = getPackageName();
    if (pm.isIgnoringBatteryOptimizations(packageName)) return;

    Intent intent = new Intent();
    intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
    intent.setData(Uri.parse("package:" + packageName));
    startActivity(intent, REQUEST_BATTERY);
}

Adbでの確認方法

  • WakeLockを取得しているか確認する
$ adb shell dumpsys power | grep "Wake Locks:" -A 10
  • AlarmManagerが設定できているかの確認
$ adb shell dumpsys alarm | grep <Package Name>
$ adb shell dumpsys deviceidle whitelist | grep <Package Name>

参考にしたサイト様

Cygwin x Windowsでアクセス権限にはまる(chmod/setfacl/getfacl)

ファイルやディレクトリを作成・編集すると、権限が変わってしまう

いつの頃かから、Eclipseなどから作成したファイル/ディレクトリの権限が060になっていたり、
gitで操作したファイルの権限が変わってしまうなど、よくわからないことが起こっていた。。

数ファイルだけだったので、毎回chmodで対応していたが、.gitディレクトリ配下まで汚染され、
のっぴきならない感じになったので、真面目に対応してみたときの備忘録

ACLを無効にして/cygdriveをマウントするが、だめだった。。

デフォルトでは、WindowsACL(Access Control List)が有効になっているので、 Windowsのアクセス権限を踏まえた設定になっている。

ACLを無効にすると良いとあったので、/etc/fstabを編集して、試してみた。

- none /cygdrive cygdrive binary,posix=0,user 0 0
+ none /cygdrive cygdrive binary,posix=0,user,noacl 0 0

が、ACLを無効にするとchmodが使えなくなる。。ので、やりたいことと違った。。

ACLをgetfaclで確認して、setfaclで設定する

ACLが悪さをしている感じだったので、うまくいくファイル/フォルダの設定を確認してみた。

$ getfacl <dir_name>
user::rwx
group::r-x
group:Authenticated Users:rwx
group:SYSTEM:rwx
group:Administrators:rwx
group:Users:r-x
mask:rwx
other:r-x
default:user::rwx
default:group::r-x
default:group:Authenticated Users:rwx
default:group:SYSTEM:rwx
default:group:Administrators:rwx
default:group:Users:r-x
default:mask:rwx
default:other:r-x

$ getfacl <file_name>
user::rwx
group::r-x
group:Authenticated Users:rwx
group:SYSTEM:rwx
group:Administrators:rwx
group:Users:r-x
mask:rwx
other:r-x

うまくいかない方を見てみると、なんか設定されていない項目などがたくさん。。

$ getfacl <dir_name>
・・・
mask:---
other:---
・・・
default:mask:---
default:other:---
・・・

こいつが原因か。。。

うまくいく設定を取り込んでみるとうまくいった!!

$ getfacl <うまくいくDir> | setfacl -f - <うまくいかないDir>
$ getfacl <うまくいくFile> | setfacl -f - <うまくいかないFile>

いつのまに変更されてしまったのかは不明ですが、ACLの設定を見直すとうまくいくのか。。。

以上!!

参考にしたサイト様

Activity破棄問題をサポートしてくれるIcepickとparceler

破棄されやすいAndroidのActivity/Fragmentたち。ライブラリを使うと簡単に状態を保存/復元できるので、その備忘録。

dependency

repositories {
  maven {url "https://clojars.org/repo/"} // Icepick
}
dependencies {
  // Icepick
  compile 'frankiesardo:icepick:3.2.0'
  provided 'frankiesardo:icepick-processor:3.2.0'
  
  // parceler
  compile 'org.parceler:parceler-api:1.1.9'
  annotationProcessor 'org.parceler:parceler:1.1.9'
}

Icepick

Activity

class ExampleActivity extends Activity {
  @State String username; // 保存するフィールドには、@Stateをつける

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Icepick.restoreInstanceState(this, savedInstanceState); // 復元
  }

  @Override public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    Icepick.saveInstanceState(this, outState); // 保存
  }
}

Fragment

public class ExampleFragment extends Fragment {
    @State String username;

    @Override
    public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
        super.onViewStateRestored(savedInstanceState);
        Icepick.restoreInstanceState(this, savedInstanceState);  // 復元
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Icepick.saveInstanceState(this, outState);    // 保存
    }
}

Custom View

class CustomView extends View {
  @State int selectedPosition;

  @Override public void onRestoreInstanceState(Parcelable state) {
    super.onRestoreInstanceState(Icepick.restoreInstanceState(this, state)); // 復元
  }
  
  @Override public Parcelable onSaveInstanceState() {
    return Icepick.saveInstanceState(this, super.onSaveInstanceState());  // 保存
  }
}

parceler

対象のクラスに@Parcelをつける

@Parcel
public class Example {
    public String name;

    public Example(String name) {
        this.name = name;
    }
}

保存/復元はこんな感じ

// 保存
Parcelable wrapped = Parcels.wrap(new Example("Andy")); 

// 復元
Example example = Parcels.unwrap(wrapped);
example.getName(); // Andy

parceler + Icepick

IcepickのBundlerを使ってwrap/unwrapする

@Parcel
public class Example {
    public String name;

    public static class Bundler implements icepick.Bundler<Example> {  // icepickのBundlerを個別に定義
        @Override
        public void put(String key, Example example, Bundle bundle) {
            bundle.putParcelable(key, Parcels.wrap(example));
        }

        @Override
        public Example get(String key, Bundle bundle) {
            return Parcels.unwrap(bundle.getParcelable(key));
        }
    }
}

@State()にBundlerを指定する

public class ExampleFragment extends Fragment {

    @State(Example.Bundler.class) Example example; 
}

参考にしたサイト様

Androidでネットワークの状態を確認/判定する

ネットワーク確認

ConnectivityManager cm = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();

// 接続状態状況の判定
boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting();
                      
// ネットワークタイプの判定
boolean isWiFi = activeNetwork.getType() == ConnectivityManager.TYPE_WIFI;

ネットワーク確認:Wifiのみ

WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
WifiInfo wifiInfo = wm.getConnectionInfo();
 
WifiInfo.SupplicantState state = wifiInfo.getSupplicantState();

接続の変化はブロードキャストレシーバーで受け取る

<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>

参考にしたサイト様